Haydn: Part 1 (#2007)
* Haydn: Part 1 Based on my reverse of audio 11.0.0. As always, core implementation under LGPLv3 for the same reasons as for Amadeus. This place the bases of a more flexible audio system while making audout & audin accurate. This have the following improvements: - Complete reimplementation of audout and audin. - Audin currently only have a dummy backend. - Dramatically reduce CPU usage by up to 50% in common cases (SoundIO and OpenAL). - Audio Renderer now can output to 5.1 devices when supported. - Audio Renderer init its backend on demand instead of keeping two up all the time. - All backends implementation are now in their own project. - Ryujinx.Audio.Renderer was renamed Ryujinx.Audio and was refactored because of this. As a note, games having issues with OpenAL haven't improved and will not because of OpenAL design (stopping when buffers finish playing causing possible audio "pops" when buffers are very small). * Update for latest hexkyz's edits on Switchbrew * audren: Rollback channel configuration changes * Address gdkchan's comments * Fix typo in OpenAL backend driver * Address last comments * Fix a nit * Address gdkchan's comments
This commit is contained in:
parent
1c49089ff0
commit
f556c80d02
249 changed files with 5614 additions and 2712 deletions
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// 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.Common;
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class AdpcmDataSourceCommandVersion1 : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
||||
public float Pitch { get; }
|
||||
|
||||
public WaveBuffer[] WaveBuffers { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public ulong AdpcmParameter { get; }
|
||||
public ulong AdpcmParameterSize { get; }
|
||||
|
||||
public DecodingBehaviour DecodingBehaviour { get; }
|
||||
|
||||
public AdpcmDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
OutputBufferIndex = outputBufferIndex;
|
||||
SampleRate = serverState.SampleRate;
|
||||
Pitch = serverState.Pitch;
|
||||
|
||||
WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
|
||||
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
|
||||
|
||||
WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
|
||||
}
|
||||
|
||||
AdpcmParameter = serverState.DataSourceStateAddressInfo.GetReference(true);
|
||||
AdpcmParameterSize = serverState.DataSourceStateAddressInfo.Size;
|
||||
State = state;
|
||||
DecodingBehaviour = serverState.DecodingBehaviour;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
|
||||
{
|
||||
State = State,
|
||||
SourceSampleRate = SampleRate,
|
||||
SampleFormat = SampleFormat.Adpcm,
|
||||
Pitch = Pitch,
|
||||
DecodingBehaviour = DecodingBehaviour,
|
||||
WaveBuffers = WaveBuffers,
|
||||
ExtraParameter = AdpcmParameter,
|
||||
ExtraParameterSize = AdpcmParameterSize,
|
||||
ChannelIndex = 0,
|
||||
ChannelCount = 1,
|
||||
};
|
||||
|
||||
DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
205
Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
Normal file
205
Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
Normal file
|
@ -0,0 +1,205 @@
|
|||
//
|
||||
// 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.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader;
|
||||
using CpuAddress = System.UInt64;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class AuxiliaryBufferCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.AuxiliaryBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint InputBufferIndex { get; }
|
||||
public uint OutputBufferIndex { get; }
|
||||
|
||||
public AuxiliaryBufferAddresses BufferInfo { get; }
|
||||
|
||||
public CpuAddress InputBuffer { get; }
|
||||
public CpuAddress OutputBuffer { get; }
|
||||
public uint CountMax { get; }
|
||||
public uint UpdateCount { get; }
|
||||
public uint WriteOffset { get; }
|
||||
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
public AuxiliaryBufferCommand(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset,
|
||||
ref AuxiliaryBufferAddresses sendBufferInfo, bool isEnabled, uint countMax,
|
||||
CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
InputBufferIndex = bufferOffset + inputBufferOffset;
|
||||
OutputBufferIndex = bufferOffset + outputBufferOffset;
|
||||
BufferInfo = sendBufferInfo;
|
||||
InputBuffer = inputBuffer;
|
||||
OutputBuffer = outputBuffer;
|
||||
CountMax = countMax;
|
||||
UpdateCount = updateCount;
|
||||
WriteOffset = writeOffset;
|
||||
IsEffectEnabled = isEnabled;
|
||||
}
|
||||
|
||||
private uint Read(IVirtualMemoryManager memoryManager, ulong bufferAddress, uint countMax, Span<int> outBuffer, uint count, uint readOffset, uint updateCount)
|
||||
{
|
||||
if (countMax == 0 || bufferAddress == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint targetReadOffset = readOffset + AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo);
|
||||
|
||||
if (targetReadOffset > countMax)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint remaining = count;
|
||||
|
||||
uint outBufferOffset = 0;
|
||||
|
||||
while (remaining != 0)
|
||||
{
|
||||
uint countToWrite = Math.Min(countMax - targetReadOffset, remaining);
|
||||
|
||||
memoryManager.Read(bufferAddress + targetReadOffset * sizeof(int), MemoryMarshal.Cast<int, byte>(outBuffer.Slice((int)outBufferOffset, (int)countToWrite)));
|
||||
|
||||
targetReadOffset = (targetReadOffset + countToWrite) % countMax;
|
||||
remaining -= countToWrite;
|
||||
outBufferOffset += countToWrite;
|
||||
}
|
||||
|
||||
if (updateCount != 0)
|
||||
{
|
||||
uint newReadOffset = (AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo) + updateCount) % countMax;
|
||||
|
||||
AuxiliaryBufferInfo.SetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo, newReadOffset);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan<int> buffer, uint count, uint writeOffset, uint updateCount)
|
||||
{
|
||||
if (countMax == 0 || outBufferAddress == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo);
|
||||
|
||||
if (targetWriteOffset > countMax)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint remaining = count;
|
||||
|
||||
uint inBufferOffset = 0;
|
||||
|
||||
while (remaining != 0)
|
||||
{
|
||||
uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining);
|
||||
|
||||
memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast<int, byte>(buffer.Slice((int)inBufferOffset, (int)countToWrite)));
|
||||
|
||||
targetWriteOffset = (targetWriteOffset + countToWrite) % countMax;
|
||||
remaining -= countToWrite;
|
||||
inBufferOffset += countToWrite;
|
||||
}
|
||||
|
||||
if (updateCount != 0)
|
||||
{
|
||||
uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo) + updateCount) % countMax;
|
||||
|
||||
AuxiliaryBufferInfo.SetWriteOffset(memoryManager, BufferInfo.SendBufferInfo, newWriteOffset);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
Span<float> inputBuffer = context.GetBuffer((int)InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer((int)OutputBufferIndex);
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
Span<int> inputBufferInt = MemoryMarshal.Cast<float, int>(inputBuffer);
|
||||
Span<int> outputBufferInt = MemoryMarshal.Cast<float, int>(outputBuffer);
|
||||
|
||||
// Convert input data to the target format for user (int)
|
||||
DataSourceHelper.ToInt(inputBufferInt, inputBuffer, outputBuffer.Length);
|
||||
|
||||
// Send the input to the user
|
||||
Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
|
||||
|
||||
// Convert back to float just in case it's reused
|
||||
DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length);
|
||||
|
||||
// Retrieve the input from user
|
||||
uint readResult = Read(context.MemoryManager, InputBuffer, CountMax, outputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
|
||||
|
||||
// Convert the outputBuffer back to the target format of the renderer (float)
|
||||
DataSourceHelper.ToFloat(outputBuffer, outputBufferInt, outputBuffer.Length);
|
||||
|
||||
if (readResult != context.SampleCount)
|
||||
{
|
||||
outputBuffer.Slice((int)readResult, (int)context.SampleCount - (int)readResult).Fill(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ZeroFill(context.MemoryManager, BufferInfo.SendBufferInfo, Unsafe.SizeOf<AuxiliaryBufferInfo>());
|
||||
ZeroFill(context.MemoryManager, BufferInfo.ReturnBufferInfo, Unsafe.SizeOf<AuxiliaryBufferInfo>());
|
||||
|
||||
if (InputBufferIndex != OutputBufferIndex)
|
||||
{
|
||||
inputBuffer.CopyTo(outputBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ZeroFill(IVirtualMemoryManager memoryManager, ulong address, int size)
|
||||
{
|
||||
ulong endAddress = address + (ulong)size;
|
||||
|
||||
while (address + 7UL < endAddress)
|
||||
{
|
||||
memoryManager.Write(address, 0UL);
|
||||
address += 8;
|
||||
}
|
||||
|
||||
while (address < endAddress)
|
||||
{
|
||||
memoryManager.Write(address, (byte)0);
|
||||
address++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
89
Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
Normal file
89
Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// 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 System;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class BiquadFilterCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.BiquadFilter;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public BiquadFilterParameter Parameter { get; }
|
||||
public Memory<BiquadFilterState> BiquadFilterState { get; }
|
||||
public int InputBufferIndex { get; }
|
||||
public int OutputBufferIndex { get; }
|
||||
public bool NeedInitialization { get; }
|
||||
|
||||
public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
|
||||
{
|
||||
Parameter = filter;
|
||||
BiquadFilterState = biquadFilterStateMemory;
|
||||
InputBufferIndex = baseIndex + inputBufferOffset;
|
||||
OutputBufferIndex = baseIndex + outputBufferOffset;
|
||||
NeedInitialization = needInitialization;
|
||||
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
}
|
||||
|
||||
private void ProcessBiquadFilter(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
||||
{
|
||||
const int fixedPointPrecisionForParameter = 14;
|
||||
|
||||
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);
|
||||
|
||||
ref BiquadFilterState state = ref BiquadFilterState.Span[0];
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = inputBuffer[i];
|
||||
float output = input * a0 + state.Z1;
|
||||
|
||||
state.Z1 = input * a1 + output * b1 + state.Z2;
|
||||
state.Z2 = input * a2 + output * b2;
|
||||
|
||||
outputBuffer[i] = output;
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
Span<float> outputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
|
||||
if (NeedInitialization)
|
||||
{
|
||||
BiquadFilterState.Span[0] = new BiquadFilterState();
|
||||
}
|
||||
|
||||
ProcessBiquadFilter(outputBuffer, outputBuffer, context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// 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.Sink;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class CircularBufferSinkCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.CircularBufferSink;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort[] Input { get; }
|
||||
public uint InputCount { get; }
|
||||
|
||||
public ulong CircularBuffer { get; }
|
||||
public ulong CircularBufferSize { get; }
|
||||
public ulong CurrentOffset { get; }
|
||||
|
||||
public CircularBufferSinkCommand(uint bufferOffset, ref CircularBufferParameter parameter, ref AddressInfo circularBufferAddressInfo, uint currentOffset, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
Input = new ushort[Constants.ChannelCountMax];
|
||||
InputCount = parameter.InputCount;
|
||||
|
||||
for (int i = 0; i < InputCount; i++)
|
||||
{
|
||||
Input[i] = (ushort)(bufferOffset + parameter.Input[i]);
|
||||
}
|
||||
|
||||
CircularBuffer = circularBufferAddressInfo.GetReference(true);
|
||||
CircularBufferSize = parameter.BufferSize;
|
||||
CurrentOffset = currentOffset;
|
||||
|
||||
Debug.Assert(CircularBuffer != 0);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
const int targetChannelCount = 2;
|
||||
|
||||
ulong currentOffset = CurrentOffset;
|
||||
|
||||
if (CircularBufferSize > 0)
|
||||
{
|
||||
for (int i = 0; i < InputCount; i++)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(Input[i]);
|
||||
|
||||
ulong targetOffset = CircularBuffer + currentOffset;
|
||||
|
||||
for (int y = 0; y < context.SampleCount; y++)
|
||||
{
|
||||
context.MemoryManager.Write(targetOffset + (ulong)y * targetChannelCount, PcmHelper.Saturate(inputBuffer[y]));
|
||||
}
|
||||
|
||||
currentOffset += context.SampleCount * targetChannelCount;
|
||||
|
||||
if (currentOffset >= CircularBufferSize)
|
||||
{
|
||||
currentOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
Normal file
41
Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// 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.Dsp.Command
|
||||
{
|
||||
public class ClearMixBufferCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.ClearMixBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ClearMixBufferCommand(int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
context.Buffers.Span.Fill(0);
|
||||
}
|
||||
}
|
||||
}
|
124
Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs
Normal file
124
Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// 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.Integration;
|
||||
using Ryujinx.Audio.Renderer.Server;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class CommandList
|
||||
{
|
||||
public ulong StartTime { get; private set; }
|
||||
public ulong EndTime { get; private set; }
|
||||
public uint SampleCount { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
||||
public Memory<float> Buffers { get; }
|
||||
public uint BufferCount { get; }
|
||||
|
||||
public List<ICommand> Commands { get; }
|
||||
|
||||
public IVirtualMemoryManager MemoryManager { get; }
|
||||
|
||||
public IHardwareDevice OutputDevice { get; private set; }
|
||||
|
||||
public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager,
|
||||
renderSystem.GetMixBuffer(),
|
||||
renderSystem.GetSampleCount(),
|
||||
renderSystem.GetSampleRate(),
|
||||
renderSystem.GetMixBufferCount(),
|
||||
renderSystem.GetVoiceChannelCountMax())
|
||||
{
|
||||
}
|
||||
|
||||
public CommandList(IVirtualMemoryManager memoryManager, Memory<float> mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax)
|
||||
{
|
||||
SampleCount = sampleCount;
|
||||
SampleRate = sampleRate;
|
||||
BufferCount = mixBufferCount + voiceChannelCountMax;
|
||||
Buffers = mixBuffer;
|
||||
Commands = new List<ICommand>();
|
||||
MemoryManager = memoryManager;
|
||||
}
|
||||
|
||||
public void AddCommand(ICommand command)
|
||||
{
|
||||
Commands.Add(command);
|
||||
}
|
||||
|
||||
public void AddCommand<T>(T command) where T : unmanaged, ICommand
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Memory<float> GetBufferMemory(int index)
|
||||
{
|
||||
return Buffers.Slice(index * (int)SampleCount, (int)SampleCount);
|
||||
}
|
||||
|
||||
public Span<float> GetBuffer(int index)
|
||||
{
|
||||
return Buffers.Span.Slice(index * (int)SampleCount, (int)SampleCount);
|
||||
}
|
||||
|
||||
public ulong GetTimeElapsedSinceDspStartedProcessing()
|
||||
{
|
||||
return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime;
|
||||
}
|
||||
|
||||
public void Process(IHardwareDevice outputDevice)
|
||||
{
|
||||
OutputDevice = outputDevice;
|
||||
|
||||
StartTime = (ulong)PerformanceCounter.ElapsedNanoseconds;
|
||||
|
||||
foreach (ICommand command in Commands)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
bool shouldMeter = command.ShouldMeter();
|
||||
|
||||
long startTime = 0;
|
||||
|
||||
if (shouldMeter)
|
||||
{
|
||||
startTime = PerformanceCounter.ElapsedNanoseconds;
|
||||
}
|
||||
|
||||
command.Process(this);
|
||||
|
||||
if (shouldMeter)
|
||||
{
|
||||
ulong effectiveElapsedTime = (ulong)(PerformanceCounter.ElapsedNanoseconds - startTime);
|
||||
|
||||
if (effectiveElapsedTime > command.EstimatedProcessingTime)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.AudioRenderer, $"Command {command.GetType().Name} took {effectiveElapsedTime}ns (expected {command.EstimatedProcessingTime}ns)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EndTime = (ulong)PerformanceCounter.ElapsedNanoseconds;
|
||||
}
|
||||
}
|
||||
}
|
49
Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
Normal file
49
Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// 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.Dsp.Command
|
||||
{
|
||||
public enum CommandType : byte
|
||||
{
|
||||
Invalid,
|
||||
PcmInt16DataSourceVersion1,
|
||||
PcmInt16DataSourceVersion2,
|
||||
PcmFloatDataSourceVersion1,
|
||||
PcmFloatDataSourceVersion2,
|
||||
AdpcmDataSourceVersion1,
|
||||
AdpcmDataSourceVersion2,
|
||||
Volume,
|
||||
VolumeRamp,
|
||||
BiquadFilter,
|
||||
Mix,
|
||||
MixRamp,
|
||||
MixRampGrouped,
|
||||
DepopPrepare,
|
||||
DepopForMixBuffers,
|
||||
Delay,
|
||||
Upsample,
|
||||
DownMixSurroundToStereo,
|
||||
AuxiliaryBuffer,
|
||||
DeviceSink,
|
||||
CircularBufferSink,
|
||||
Reverb,
|
||||
Reverb3d,
|
||||
Performance,
|
||||
ClearMixBuffer,
|
||||
CopyMixBuffer
|
||||
}
|
||||
}
|
52
Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
Normal file
52
Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class CopyMixBufferCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.CopyMixBuffer;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public CopyMixBufferCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)inputBufferIndex;
|
||||
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
inputBuffer.CopyTo(outputBuffer);
|
||||
}
|
||||
}
|
||||
}
|
127
Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
Normal file
127
Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
Normal file
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// 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.Common;
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DataSourceVersion2Command : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType { get; }
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
|
||||
public float Pitch { get; }
|
||||
|
||||
public WaveBuffer[] WaveBuffers { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public ulong ExtraParameter { get; }
|
||||
public ulong ExtraParameterSize { get; }
|
||||
|
||||
public uint ChannelIndex { get; }
|
||||
|
||||
public uint ChannelCount { get; }
|
||||
|
||||
public DecodingBehaviour DecodingBehaviour { get; }
|
||||
|
||||
public SampleFormat SampleFormat { get; }
|
||||
|
||||
public SampleRateConversionQuality SrcQuality { get; }
|
||||
|
||||
public DataSourceVersion2Command(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
ChannelIndex = channelIndex;
|
||||
ChannelCount = serverState.ChannelsCount;
|
||||
SampleFormat = serverState.SampleFormat;
|
||||
SrcQuality = serverState.SrcQuality;
|
||||
CommandType = GetCommandTypeBySampleFormat(SampleFormat);
|
||||
|
||||
OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
|
||||
SampleRate = serverState.SampleRate;
|
||||
Pitch = serverState.Pitch;
|
||||
|
||||
WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
|
||||
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
|
||||
|
||||
WaveBuffers[i] = voiceWaveBuffer.ToCommon(2);
|
||||
}
|
||||
|
||||
if (SampleFormat == SampleFormat.Adpcm)
|
||||
{
|
||||
ExtraParameter = serverState.DataSourceStateAddressInfo.GetReference(true);
|
||||
ExtraParameterSize = serverState.DataSourceStateAddressInfo.Size;
|
||||
}
|
||||
|
||||
State = state;
|
||||
DecodingBehaviour = serverState.DecodingBehaviour;
|
||||
}
|
||||
|
||||
private static CommandType GetCommandTypeBySampleFormat(SampleFormat sampleFormat)
|
||||
{
|
||||
switch (sampleFormat)
|
||||
{
|
||||
case SampleFormat.Adpcm:
|
||||
return CommandType.AdpcmDataSourceVersion2;
|
||||
case SampleFormat.PcmInt16:
|
||||
return CommandType.PcmInt16DataSourceVersion2;
|
||||
case SampleFormat.PcmFloat:
|
||||
return CommandType.PcmFloatDataSourceVersion2;
|
||||
default:
|
||||
throw new NotImplementedException($"{sampleFormat}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
|
||||
{
|
||||
State = State,
|
||||
SourceSampleRate = SampleRate,
|
||||
SampleFormat = SampleFormat,
|
||||
Pitch = Pitch,
|
||||
DecodingBehaviour = DecodingBehaviour,
|
||||
WaveBuffers = WaveBuffers,
|
||||
ExtraParameter = ExtraParameter,
|
||||
ExtraParameterSize = ExtraParameterSize,
|
||||
ChannelIndex = (int)ChannelIndex,
|
||||
ChannelCount = (int)ChannelCount,
|
||||
SrcQuality = SrcQuality
|
||||
};
|
||||
|
||||
DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
272
Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
Normal file
272
Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
Normal file
|
@ -0,0 +1,272 @@
|
|||
//
|
||||
// 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 Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DelayCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Delay;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public DelayParameter Parameter => _parameter;
|
||||
public Memory<DelayState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private DelayParameter _parameter;
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> 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]);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelayMono(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
float input = inputBuffer[i] * 64;
|
||||
float delayLineValue = state.DelayLines[0].Read();
|
||||
|
||||
float lowPassResult = input * inGain + delayLineValue * feedbackGain * state.LowPassBaseGain + state.LowPassZ[0] * state.LowPassFeedbackGain;
|
||||
|
||||
state.LowPassZ[0] = lowPassResult;
|
||||
|
||||
state.DelayLines[0].Update(lowPassResult);
|
||||
|
||||
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelayStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
float[] channelInput = new float[Parameter.ChannelCount];
|
||||
float[] delayLineValues = new float[Parameter.ChannelCount];
|
||||
float[] temp = new float[Parameter.ChannelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
channelInput[j] = inputBuffers[j].Span[i] * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
|
||||
temp[0] = channelInput[0] * inGain + delayLineValues[1] * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + delayLineValues[0] * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
|
||||
outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelayQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
float[] channelInput = new float[Parameter.ChannelCount];
|
||||
float[] delayLineValues = new float[Parameter.ChannelCount];
|
||||
float[] temp = new float[Parameter.ChannelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
channelInput[j] = inputBuffers[j].Span[i] * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
|
||||
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + (delayLineValues[0] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
|
||||
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
|
||||
outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelaySurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
float[] channelInput = new float[Parameter.ChannelCount];
|
||||
float[] delayLineValues = new float[Parameter.ChannelCount];
|
||||
float[] temp = new float[Parameter.ChannelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
channelInput[j] = inputBuffers[j].Span[i] * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
|
||||
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[4]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + (delayLineValues[4] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
|
||||
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
|
||||
temp[4] = channelInput[4] * inGain + (delayLineValues[0] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[4] * delayFeedbackBaseGain;
|
||||
temp[5] = channelInput[5] * inGain + delayLineValues[5] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < Parameter.ChannelCount; j++)
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
|
||||
outputBuffers[j].Span[i] = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDelay(CommandList context)
|
||||
{
|
||||
Debug.Assert(Parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
||||
{
|
||||
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]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessDelayMono(outputBuffers[0].Span, inputBuffers[0].Span, context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessDelayStereo(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessDelayQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessDelaySurround(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref DelayState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.Status == UsageState.Invalid)
|
||||
{
|
||||
state = new DelayState(ref _parameter, WorkBuffer);
|
||||
}
|
||||
else if (Parameter.Status == UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessDelay(context);
|
||||
}
|
||||
}
|
||||
}
|
103
Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
Normal file
103
Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DepopForMixBuffersCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.DepopForMixBuffers;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferOffset { get; }
|
||||
|
||||
public uint MixBufferCount { get; }
|
||||
|
||||
public float Decay { get; }
|
||||
|
||||
public Memory<float> DepopBuffer { get; }
|
||||
|
||||
private const int FixedPointPrecisionForDecay = 15;
|
||||
|
||||
public DepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint mixBufferCount, int nodeId, uint sampleRate)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
MixBufferOffset = bufferOffset;
|
||||
MixBufferCount = mixBufferCount;
|
||||
DepopBuffer = depopBuffer;
|
||||
|
||||
if (sampleRate == 48000)
|
||||
{
|
||||
Decay = 0.962189f;
|
||||
}
|
||||
else // if (sampleRate == 32000)
|
||||
{
|
||||
Decay = 0.943695f;
|
||||
}
|
||||
}
|
||||
|
||||
private float ProcessDepopMix(Span<float> buffer, float depopValue, uint sampleCount)
|
||||
{
|
||||
if (depopValue <= 0)
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue);
|
||||
|
||||
buffer[i] -= depopValue;
|
||||
}
|
||||
|
||||
return -depopValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue);
|
||||
|
||||
buffer[i] += depopValue;
|
||||
}
|
||||
|
||||
return depopValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
uint bufferCount = Math.Min(MixBufferOffset + MixBufferCount, context.BufferCount);
|
||||
|
||||
for (int i = (int)MixBufferOffset; i < bufferCount; i++)
|
||||
{
|
||||
float depopValue = DepopBuffer.Span[i];
|
||||
if (depopValue != 0)
|
||||
{
|
||||
Span<float> buffer = context.GetBuffer(i);
|
||||
|
||||
DepopBuffer.Span[i] = ProcessDepopMix(buffer, depopValue, context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
Normal file
72
Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// 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.Dsp.Command
|
||||
{
|
||||
public class DepopPrepareCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.DepopPrepare;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferCount { get; }
|
||||
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
public Memory<float> DepopBuffer { get; }
|
||||
|
||||
public DepopPrepareCommand(Memory<VoiceUpdateState> state, Memory<float> depopBuffer, uint mixBufferCount, uint bufferOffset, int nodeId, bool enabled)
|
||||
{
|
||||
Enabled = enabled;
|
||||
NodeId = nodeId;
|
||||
MixBufferCount = mixBufferCount;
|
||||
|
||||
OutputBufferIndices = new ushort[Constants.MixBufferCountMax];
|
||||
|
||||
for (int i = 0; i < Constants.MixBufferCountMax; i++)
|
||||
{
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + i);
|
||||
}
|
||||
|
||||
State = state;
|
||||
DepopBuffer = depopBuffer;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref VoiceUpdateState state = ref State.Span[0];
|
||||
|
||||
for (int i = 0; i < MixBufferCount; i++)
|
||||
{
|
||||
if (state.LastSamples[i] != 0)
|
||||
{
|
||||
DepopBuffer.Span[OutputBufferIndices[i]] += state.LastSamples[i];
|
||||
|
||||
state.LastSamples[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
Normal file
108
Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
Normal file
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// 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.Integration;
|
||||
using Ryujinx.Audio.Renderer.Server.Sink;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DeviceSinkCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.DeviceSink;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public string DeviceName { get; }
|
||||
|
||||
public int SessionId { get; }
|
||||
|
||||
public uint InputCount { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
|
||||
public Memory<float> Buffers { get; }
|
||||
|
||||
public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory<float> buffers, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0');
|
||||
SessionId = sessionId;
|
||||
InputCount = sink.Parameter.InputCount;
|
||||
InputBufferIndices = new ushort[InputCount];
|
||||
|
||||
for (int i = 0; i < InputCount; i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + sink.Parameter.Input[i]);
|
||||
}
|
||||
|
||||
if (sink.UpsamplerState != null)
|
||||
{
|
||||
Buffers = sink.UpsamplerState.OutputBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffers = buffers;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Span<float> GetBuffer(int index, int sampleCount)
|
||||
{
|
||||
return Buffers.Span.Slice(index * sampleCount, sampleCount);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
IHardwareDevice device = context.OutputDevice;
|
||||
|
||||
if (device.GetSampleRate() == Constants.TargetSampleRate)
|
||||
{
|
||||
int channelCount = (int)device.GetChannelCount();
|
||||
uint bufferCount = Math.Min(device.GetChannelCount(), InputCount);
|
||||
|
||||
const int sampleCount = Constants.TargetSampleCount;
|
||||
|
||||
short[] outputBuffer = new short[bufferCount * sampleCount];
|
||||
|
||||
for (int i = 0; i < bufferCount; i++)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = GetBuffer(InputBufferIndices[i], sampleCount);
|
||||
|
||||
for (int j = 0; j < sampleCount; j++)
|
||||
{
|
||||
outputBuffer[i + j * channelCount] = PcmHelper.Saturate(inputBuffer[j]);
|
||||
}
|
||||
}
|
||||
|
||||
device.AppendBuffer(outputBuffer, InputCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: support resampling for device only supporting something different
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// 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;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class DownMixSurroundToStereoCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.DownMixSurroundToStereo;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
|
||||
public float[] Coefficients { get; }
|
||||
|
||||
public DownMixSurroundToStereoCommand(uint bufferOffset, Span<byte> inputBufferOffset, Span<byte> outputBufferOffset, ReadOnlySpan<float> downMixParameter, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||
|
||||
for (int i = 0; i < Constants.VoiceChannelCountMax; i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + outputBufferOffset[i]);
|
||||
}
|
||||
|
||||
Coefficients = downMixParameter.ToArray();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float DownMixSurroundToStereo(ReadOnlySpan<float> coefficients, float back, float lfe, float center, float front)
|
||||
{
|
||||
return FloatingPointHelper.RoundUp(coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> frontLeft = context.GetBuffer(InputBufferIndices[0]);
|
||||
ReadOnlySpan<float> frontRight = context.GetBuffer(InputBufferIndices[1]);
|
||||
ReadOnlySpan<float> frontCenter = context.GetBuffer(InputBufferIndices[2]);
|
||||
ReadOnlySpan<float> lowFrequency = context.GetBuffer(InputBufferIndices[3]);
|
||||
ReadOnlySpan<float> backLeft = context.GetBuffer(InputBufferIndices[4]);
|
||||
ReadOnlySpan<float> backRight = context.GetBuffer(InputBufferIndices[5]);
|
||||
|
||||
Span<float> stereoLeft = context.GetBuffer(OutputBufferIndices[0]);
|
||||
Span<float> stereoRight = context.GetBuffer(OutputBufferIndices[1]);
|
||||
Span<float> unused2 = context.GetBuffer(OutputBufferIndices[2]);
|
||||
Span<float> unused3 = context.GetBuffer(OutputBufferIndices[3]);
|
||||
Span<float> unused4 = context.GetBuffer(OutputBufferIndices[4]);
|
||||
Span<float> unused5 = context.GetBuffer(OutputBufferIndices[5]);
|
||||
|
||||
for (int i = 0; i < context.SampleCount; i++)
|
||||
{
|
||||
stereoLeft[i] = DownMixSurroundToStereo(Coefficients, backLeft[i], lowFrequency[i], frontCenter[i], frontLeft[i]);
|
||||
stereoRight[i] = DownMixSurroundToStereo(Coefficients, backRight[i], lowFrequency[i], frontCenter[i], frontRight[i]);
|
||||
}
|
||||
|
||||
unused2.Fill(0);
|
||||
unused3.Fill(0);
|
||||
unused4.Fill(0);
|
||||
unused5.Fill(0);
|
||||
}
|
||||
}
|
||||
}
|
37
Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
Normal file
37
Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// 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.Dsp.Command
|
||||
{
|
||||
public interface ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType { get; }
|
||||
|
||||
public ulong EstimatedProcessingTime { get; }
|
||||
|
||||
public void Process(CommandList context);
|
||||
|
||||
public bool ShouldMeter()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
125
Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
Normal file
125
Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
Normal file
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// 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;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class MixCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Mix;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public float Volume { get; }
|
||||
|
||||
public MixCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)inputBufferIndex;
|
||||
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||
|
||||
Volume = volume;
|
||||
}
|
||||
|
||||
private void ProcessMixAvx(Span<float> outputMix, ReadOnlySpan<float> inputMix)
|
||||
{
|
||||
Vector256<float> volumeVec = Vector256.Create(Volume);
|
||||
|
||||
ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(inputMix);
|
||||
Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(outputMix);
|
||||
|
||||
int sisdStart = inputVec.Length * 8;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.Add(outputVec[i], Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec)));
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < inputMix.Length; i++)
|
||||
{
|
||||
outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMixSse41(Span<float> outputMix, ReadOnlySpan<float> inputMix)
|
||||
{
|
||||
Vector128<float> volumeVec = Vector128.Create(Volume);
|
||||
|
||||
ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(inputMix);
|
||||
Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(outputMix);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Sse.Add(outputVec[i], Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec)));
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < inputMix.Length; i++)
|
||||
{
|
||||
outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessMixSlowPath(Span<float> outputMix, ReadOnlySpan<float> inputMix)
|
||||
{
|
||||
for (int i = 0; i < inputMix.Length; i++)
|
||||
{
|
||||
outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMix(Span<float> outputMix, ReadOnlySpan<float> inputMix)
|
||||
{
|
||||
if (Avx.IsSupported)
|
||||
{
|
||||
ProcessMixAvx(outputMix, inputMix);
|
||||
}
|
||||
else if (Sse41.IsSupported)
|
||||
{
|
||||
ProcessMixSse41(outputMix, inputMix);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessMixSlowPath(outputMix, inputMix);
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
ProcessMix(outputBuffer, inputBuffer);
|
||||
}
|
||||
}
|
||||
}
|
83
Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
Normal file
83
Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
Normal file
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// 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.Dsp.Command
|
||||
{
|
||||
public class MixRampCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.MixRamp;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public float Volume0 { get; }
|
||||
public float Volume1 { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public int LastSampleIndex { get; }
|
||||
|
||||
public MixRampCommand(float volume0, float volume1, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory<VoiceUpdateState> state, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)inputBufferIndex;
|
||||
OutputBufferIndex = (ushort)outputBufferIndex;
|
||||
|
||||
Volume0 = volume0;
|
||||
Volume1 = volume1;
|
||||
|
||||
State = state;
|
||||
LastSampleIndex = lastSampleIndex;
|
||||
}
|
||||
|
||||
private float ProcessMixRamp(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int sampleCount)
|
||||
{
|
||||
float ramp = (Volume1 - Volume0) / sampleCount;
|
||||
float volume = Volume0;
|
||||
float state = 0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
|
||||
|
||||
outputBuffer[i] += state;
|
||||
volume += ramp;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
State.Span[0].LastSamples[LastSampleIndex] = ProcessMixRamp(outputBuffer, inputBuffer, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
106
Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
Normal file
106
Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
Normal file
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// 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.Dsp.Command
|
||||
{
|
||||
public class MixRampGroupedCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.MixRampGrouped;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint MixBufferCount { get; }
|
||||
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
|
||||
public float[] Volume0 { get; }
|
||||
public float[] Volume1 { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
|
||||
public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> volume0, Span<float> volume1, Memory<VoiceUpdateState> state, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
MixBufferCount = mixBufferCount;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndices = new ushort[Constants.MixBufferCountMax];
|
||||
OutputBufferIndices = new ushort[Constants.MixBufferCountMax];
|
||||
Volume0 = new float[Constants.MixBufferCountMax];
|
||||
Volume1 = new float[Constants.MixBufferCountMax];
|
||||
|
||||
for (int i = 0; i < mixBufferCount; i++)
|
||||
{
|
||||
InputBufferIndices[i] = (ushort)inputBufferIndex;
|
||||
OutputBufferIndices[i] = (ushort)(outputBufferIndex + i);
|
||||
|
||||
Volume0[i] = volume0[i];
|
||||
Volume1[i] = volume1[i];
|
||||
}
|
||||
|
||||
State = state;
|
||||
}
|
||||
|
||||
private float ProcessMixRampGrouped(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float volume0, float volume1, int sampleCount)
|
||||
{
|
||||
float ramp = (volume1 - volume0) / sampleCount;
|
||||
float volume = volume0;
|
||||
float state = 0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
|
||||
|
||||
outputBuffer[i] += state;
|
||||
volume += ramp;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
for (int i = 0; i < MixBufferCount; i++)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndices[i]);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndices[i]);
|
||||
|
||||
float volume0 = Volume0[i];
|
||||
float volume1 = Volume1[i];
|
||||
|
||||
ref VoiceUpdateState state = ref State.Span[0];
|
||||
|
||||
if (volume0 != 0 || volume1 != 0)
|
||||
{
|
||||
state.LastSamples[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.LastSamples[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// 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.Common;
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class PcmFloatDataSourceCommandVersion1 : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
public uint ChannelIndex { get; }
|
||||
|
||||
public uint ChannelCount { get; }
|
||||
|
||||
public float Pitch { get; }
|
||||
|
||||
public WaveBuffer[] WaveBuffers { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
public DecodingBehaviour DecodingBehaviour { get; }
|
||||
|
||||
public PcmFloatDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
|
||||
SampleRate = serverState.SampleRate;
|
||||
ChannelIndex = channelIndex;
|
||||
ChannelCount = serverState.ChannelsCount;
|
||||
Pitch = serverState.Pitch;
|
||||
|
||||
WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
|
||||
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
|
||||
|
||||
WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
|
||||
}
|
||||
|
||||
State = state;
|
||||
DecodingBehaviour = serverState.DecodingBehaviour;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
|
||||
{
|
||||
State = State,
|
||||
SourceSampleRate = SampleRate,
|
||||
SampleFormat = SampleFormat.PcmInt16,
|
||||
Pitch = Pitch,
|
||||
DecodingBehaviour = DecodingBehaviour,
|
||||
WaveBuffers = WaveBuffers,
|
||||
ExtraParameter = 0,
|
||||
ExtraParameterSize = 0,
|
||||
ChannelIndex = (int)ChannelIndex,
|
||||
ChannelCount = (int)ChannelCount,
|
||||
};
|
||||
|
||||
DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// 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.Common;
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using System;
|
||||
using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class PcmInt16DataSourceCommandVersion1 : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort OutputBufferIndex { get; }
|
||||
public uint SampleRate { get; }
|
||||
public uint ChannelIndex { get; }
|
||||
|
||||
public uint ChannelCount { get; }
|
||||
|
||||
public float Pitch { get; }
|
||||
|
||||
public WaveBuffer[] WaveBuffers { get; }
|
||||
|
||||
public Memory<VoiceUpdateState> State { get; }
|
||||
public DecodingBehaviour DecodingBehaviour { get; }
|
||||
|
||||
public PcmInt16DataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory<VoiceUpdateState> state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
|
||||
SampleRate = serverState.SampleRate;
|
||||
ChannelIndex = channelIndex;
|
||||
ChannelCount = serverState.ChannelsCount;
|
||||
Pitch = serverState.Pitch;
|
||||
|
||||
WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
|
||||
|
||||
for (int i = 0; i < WaveBuffers.Length; i++)
|
||||
{
|
||||
ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
|
||||
|
||||
WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
|
||||
}
|
||||
|
||||
State = state;
|
||||
DecodingBehaviour = serverState.DecodingBehaviour;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation()
|
||||
{
|
||||
State = State,
|
||||
SourceSampleRate = SampleRate,
|
||||
SampleFormat = SampleFormat.PcmInt16,
|
||||
Pitch = Pitch,
|
||||
DecodingBehaviour = DecodingBehaviour,
|
||||
WaveBuffers = WaveBuffers,
|
||||
ExtraParameter = 0,
|
||||
ExtraParameterSize = 0,
|
||||
ChannelIndex = (int)ChannelIndex,
|
||||
ChannelCount = (int)ChannelCount,
|
||||
};
|
||||
|
||||
DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, info, context.SampleRate, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
64
Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
Normal file
64
Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// 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.Performance;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class PerformanceCommand : ICommand
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Invalid,
|
||||
Start,
|
||||
End
|
||||
}
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Performance;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public PerformanceEntryAddresses PerformanceEntryAddresses { get; }
|
||||
|
||||
public Type PerformanceType { get; set; }
|
||||
|
||||
public PerformanceCommand(ref PerformanceEntryAddresses performanceEntryAddresses, Type performanceType, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
PerformanceEntryAddresses = performanceEntryAddresses;
|
||||
PerformanceType = performanceType;
|
||||
NodeId = nodeId;
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
if (PerformanceType == Type.Start)
|
||||
{
|
||||
PerformanceEntryAddresses.SetStartTime(context.GetTimeElapsedSinceDspStartedProcessing());
|
||||
}
|
||||
else if (PerformanceType == Type.End)
|
||||
{
|
||||
PerformanceEntryAddresses.SetProcessingTime(context.GetTimeElapsedSinceDspStartedProcessing());
|
||||
PerformanceEntryAddresses.IncrementEntryCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
263
Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
Normal file
263
Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
Normal file
|
@ -0,0 +1,263 @@
|
|||
//
|
||||
// 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 Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class Reverb3dCommand : ICommand
|
||||
{
|
||||
private static readonly int[] OutputEarlyIndicesTableMono = new int[20] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[1] { 0 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableStereo = new int[20] { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[2] { 0, 1 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[20] { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableSurround = new int[40] { 4, 5, 0, 5, 0, 5, 1, 5, 1, 5, 1, 5, 1, 5, 2, 5, 2, 5, 2, 5, 1, 5, 1, 5, 1, 5, 0, 5, 0, 5, 0, 5, 0, 5, 3, 5, 3, 5, 3, 5 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[40] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[6] { 0, 1, 2, 3, -1, 3 };
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Reverb3d;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public Reverb3dParameter Parameter => _parameter;
|
||||
public Memory<Reverb3dState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private Reverb3dParameter _parameter;
|
||||
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessReverb3dMono(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableMono, TargetEarlyDelayLineIndicesTableMono, TargetOutputFeedbackIndicesTableMono);
|
||||
}
|
||||
|
||||
private void ProcessReverb3dStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableStereo, TargetEarlyDelayLineIndicesTableStereo, TargetOutputFeedbackIndicesTableStereo);
|
||||
}
|
||||
|
||||
private void ProcessReverb3dQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableQuadraphonic, TargetEarlyDelayLineIndicesTableQuadraphonic, TargetOutputFeedbackIndicesTableQuadraphonic);
|
||||
}
|
||||
|
||||
private void ProcessReverb3dSurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverb3dGeneric(outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableSurround, TargetEarlyDelayLineIndicesTableSurround, TargetOutputFeedbackIndicesTableSurround);
|
||||
}
|
||||
|
||||
private void ProcessReverb3dGeneric(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable)
|
||||
{
|
||||
const int delayLineSampleIndexOffset = 1;
|
||||
|
||||
ref Reverb3dState state = ref State.Span[0];
|
||||
|
||||
bool isMono = Parameter.ChannelCount == 1;
|
||||
bool isSurround = Parameter.ChannelCount == 6;
|
||||
|
||||
float[] outputValues = new float[Constants.ChannelCountMax];
|
||||
float[] channelInput = new float[Parameter.ChannelCount];
|
||||
float[] feedbackValues = new float[4];
|
||||
float[] feedbackOutputValues = new float[4];
|
||||
float[] values = new float[4];
|
||||
|
||||
for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
|
||||
{
|
||||
outputValues.AsSpan().Fill(0);
|
||||
|
||||
float tapOut = state.PreDelayLine.TapUnsafe(state.ReflectionDelayTime, delayLineSampleIndexOffset);
|
||||
|
||||
for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++)
|
||||
{
|
||||
int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i];
|
||||
int outputIndex = outputEarlyIndicesTable[i];
|
||||
|
||||
float tempTapOut = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], delayLineSampleIndexOffset);
|
||||
|
||||
outputValues[outputIndex] += tempTapOut * state.EarlyGain[earlyDelayIndex];
|
||||
}
|
||||
|
||||
float targetPreDelayValue = 0;
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
channelInput[channelIndex] = inputBuffers[channelIndex].Span[sampleIndex];
|
||||
targetPreDelayValue += channelInput[channelIndex];
|
||||
}
|
||||
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
outputValues[i] *= state.EarlyReflectionsGain;
|
||||
}
|
||||
|
||||
state.PreviousPreDelayValue = (targetPreDelayValue * state.TargetPreDelayGain) + (state.PreviousPreDelayValue * state.PreviousPreDelayGain);
|
||||
|
||||
state.PreDelayLine.Update(state.PreviousPreDelayValue);
|
||||
|
||||
for (int i = 0; i < state.FdnDelayLines.Length; i++)
|
||||
{
|
||||
float fdnValue = state.FdnDelayLines[i].Read();
|
||||
|
||||
float feedbackOutputValue = fdnValue * state.DecayDirectFdnGain[i] + state.PreviousFeedbackOutputDecayed[i];
|
||||
|
||||
state.PreviousFeedbackOutputDecayed[i] = (fdnValue * state.DecayCurrentFdnGain[i]) + (feedbackOutputValue * state.DecayCurrentOutputGain[i]);
|
||||
|
||||
feedbackOutputValues[i] = feedbackOutputValue;
|
||||
}
|
||||
|
||||
feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1];
|
||||
feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3];
|
||||
feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3];
|
||||
feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2];
|
||||
|
||||
for (int i = 0; i < state.DecayDelays1.Length; i++)
|
||||
{
|
||||
float temp = state.DecayDelays1[i].Update(tapOut * state.LateReverbGain + feedbackValues[i]);
|
||||
|
||||
values[i] = state.DecayDelays2[i].Update(temp);
|
||||
|
||||
state.FdnDelayLines[i].Update(values[i]);
|
||||
}
|
||||
|
||||
for (int channelIndex = 0; channelIndex < targetOutputFeedbackIndicesTable.Length; channelIndex++)
|
||||
{
|
||||
int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[channelIndex];
|
||||
|
||||
if (targetOutputFeedbackIndex >= 0)
|
||||
{
|
||||
outputBuffers[channelIndex].Span[sampleIndex] = (outputValues[channelIndex] + values[targetOutputFeedbackIndex] + channelInput[channelIndex] * state.DryGain);
|
||||
}
|
||||
}
|
||||
|
||||
if (isMono)
|
||||
{
|
||||
outputBuffers[0].Span[sampleIndex] += values[1];
|
||||
}
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
outputBuffers[4].Span[sampleIndex] += (outputValues[4] + state.BackLeftDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessReverb3d(CommandList context)
|
||||
{
|
||||
Debug.Assert(Parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
||||
{
|
||||
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]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessReverb3dMono(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessReverb3dStereo(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessReverb3dQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessReverb3dSurround(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref Reverb3dState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.ParameterStatus == UsageState.Invalid)
|
||||
{
|
||||
state = new Reverb3dState(ref _parameter, WorkBuffer);
|
||||
}
|
||||
else if (Parameter.ParameterStatus == UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessReverb3d(context);
|
||||
}
|
||||
}
|
||||
}
|
284
Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
Normal file
284
Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
Normal file
|
@ -0,0 +1,284 @@
|
|||
//
|
||||
// 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 ReverbCommand : ICommand
|
||||
{
|
||||
private static readonly int[] OutputEarlyIndicesTableMono = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableMono = new int[4] { 0, 0, 0, 0 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableStereo = new int[10] { 0, 0, 1, 1, 0, 1, 0, 0, 1, 1 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableStereo = new int[4] { 0, 0, 1, 1 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[4] { 2, 0, 3, 1 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[10] { 0, 0, 1, 1, 0, 1, 2, 2, 3, 3 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
private static readonly int[] OutputIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
|
||||
|
||||
private static readonly int[] OutputEarlyIndicesTableSurround = new int[20] { 0, 5, 0, 5, 1, 5, 1, 5, 4, 5, 4, 5, 2, 5, 2, 5, 3, 5, 3, 5 };
|
||||
private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[20] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 };
|
||||
private static readonly int[] OutputIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 };
|
||||
private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 };
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Reverb;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ReverbParameter Parameter => _parameter;
|
||||
public Memory<ReverbState> State { get; }
|
||||
public ulong WorkBuffer { get; }
|
||||
public ushort[] OutputBufferIndices { get; }
|
||||
public ushort[] InputBufferIndices { get; }
|
||||
public bool IsLongSizePreDelaySupported { get; }
|
||||
|
||||
public bool IsEffectEnabled { get; }
|
||||
|
||||
private ReverbParameter _parameter;
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
NodeId = nodeId;
|
||||
_parameter = parameter;
|
||||
State = state;
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
IsLongSizePreDelaySupported = isLongSizePreDelaySupported;
|
||||
}
|
||||
|
||||
private void ProcessReverbMono(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableMono,
|
||||
TargetEarlyDelayLineIndicesTableMono,
|
||||
TargetOutputFeedbackIndicesTableMono,
|
||||
OutputIndicesTableMono);
|
||||
}
|
||||
|
||||
private void ProcessReverbStereo(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableStereo,
|
||||
TargetEarlyDelayLineIndicesTableStereo,
|
||||
TargetOutputFeedbackIndicesTableStereo,
|
||||
OutputIndicesTableStereo);
|
||||
}
|
||||
|
||||
private void ProcessReverbQuadraphonic(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableQuadraphonic,
|
||||
TargetEarlyDelayLineIndicesTableQuadraphonic,
|
||||
TargetOutputFeedbackIndicesTableQuadraphonic,
|
||||
OutputIndicesTableQuadraphonic);
|
||||
}
|
||||
|
||||
private void ProcessReverbSurround(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount)
|
||||
{
|
||||
ProcessReverbGeneric(outputBuffers,
|
||||
inputBuffers,
|
||||
sampleCount,
|
||||
OutputEarlyIndicesTableSurround,
|
||||
TargetEarlyDelayLineIndicesTableSurround,
|
||||
TargetOutputFeedbackIndicesTableSurround,
|
||||
OutputIndicesTableSurround);
|
||||
}
|
||||
|
||||
private void ProcessReverbGeneric(Memory<float>[] outputBuffers, ReadOnlyMemory<float>[] inputBuffers, uint sampleCount, ReadOnlySpan<int> outputEarlyIndicesTable, ReadOnlySpan<int> targetEarlyDelayLineIndicesTable, ReadOnlySpan<int> targetOutputFeedbackIndicesTable, ReadOnlySpan<int> outputIndicesTable)
|
||||
{
|
||||
ref ReverbState state = ref State.Span[0];
|
||||
|
||||
bool isSurround = Parameter.ChannelCount == 6;
|
||||
|
||||
float reverbGain = FixedPointHelper.ToFloat(Parameter.ReverbGain, FixedPointPrecision);
|
||||
float lateGain = FixedPointHelper.ToFloat(Parameter.LateGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
|
||||
float[] outputValues = new float[Constants.ChannelCountMax];
|
||||
float[] feedbackValues = new float[4];
|
||||
float[] feedbackOutputValues = new float[4];
|
||||
float[] channelInput = new float[Parameter.ChannelCount];
|
||||
|
||||
for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
|
||||
{
|
||||
outputValues.AsSpan().Fill(0);
|
||||
|
||||
for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++)
|
||||
{
|
||||
int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i];
|
||||
int outputIndex = outputEarlyIndicesTable[i];
|
||||
|
||||
float tapOutput = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], 0);
|
||||
|
||||
outputValues[outputIndex] += tapOutput * state.EarlyGain[earlyDelayIndex];
|
||||
}
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
outputValues[5] *= 0.2f;
|
||||
}
|
||||
|
||||
float targetPreDelayValue = 0;
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
channelInput[channelIndex] = inputBuffers[channelIndex].Span[sampleIndex] * 64;
|
||||
targetPreDelayValue += channelInput[channelIndex] * reverbGain;
|
||||
}
|
||||
|
||||
state.PreDelayLine.Update(targetPreDelayValue);
|
||||
|
||||
float lateValue = state.PreDelayLine.Tap(state.PreDelayLineDelayTime) * lateGain;
|
||||
|
||||
for (int i = 0; i < state.FdnDelayLines.Length; i++)
|
||||
{
|
||||
feedbackOutputValues[i] = state.FdnDelayLines[i].Read() * state.HighFrequencyDecayDirectGain[i] + state.PreviousFeedbackOutput[i] * state.HighFrequencyDecayPreviousGain[i];
|
||||
state.PreviousFeedbackOutput[i] = feedbackOutputValues[i];
|
||||
}
|
||||
|
||||
feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1];
|
||||
feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3];
|
||||
feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3];
|
||||
feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2];
|
||||
|
||||
for (int i = 0; i < state.FdnDelayLines.Length; i++)
|
||||
{
|
||||
feedbackOutputValues[i] = state.DecayDelays[i].Update(feedbackValues[i] + lateValue);
|
||||
state.FdnDelayLines[i].Update(feedbackOutputValues[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < targetOutputFeedbackIndicesTable.Length; i++)
|
||||
{
|
||||
int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[i];
|
||||
int outputIndex = outputIndicesTable[i];
|
||||
|
||||
if (targetOutputFeedbackIndex >= 0)
|
||||
{
|
||||
outputValues[outputIndex] += feedbackOutputValues[targetOutputFeedbackIndex];
|
||||
}
|
||||
}
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
outputValues[4] += state.BackLeftDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
|
||||
}
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
{
|
||||
outputBuffers[channelIndex].Span[sampleIndex] = (outputValues[channelIndex] * outGain + channelInput[channelIndex] * dryGain) / 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessReverb(CommandList context)
|
||||
{
|
||||
Debug.Assert(Parameter.IsChannelCountValid());
|
||||
|
||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
||||
{
|
||||
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]);
|
||||
}
|
||||
|
||||
switch (Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
ProcessReverbMono(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 2:
|
||||
ProcessReverbStereo(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 4:
|
||||
ProcessReverbQuadraphonic(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
case 6:
|
||||
ProcessReverbSurround(outputBuffers, inputBuffers, context.SampleCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"{Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
||||
{
|
||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||
{
|
||||
context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ref ReverbState state = ref State.Span[0];
|
||||
|
||||
if (IsEffectEnabled)
|
||||
{
|
||||
if (Parameter.Status == Server.Effect.UsageState.Invalid)
|
||||
{
|
||||
state = new ReverbState(ref _parameter, WorkBuffer, IsLongSizePreDelaySupported);
|
||||
}
|
||||
else if (Parameter.Status == Server.Effect.UsageState.New)
|
||||
{
|
||||
state.UpdateParameter(ref _parameter);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessReverb(context);
|
||||
}
|
||||
}
|
||||
}
|
87
Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
Normal file
87
Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
//
|
||||
// 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.Upsampler;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class UpsampleCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Upsample;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public uint BufferCount { get; }
|
||||
public uint InputBufferIndex { get; }
|
||||
public uint InputSampleCount { get; }
|
||||
public uint InputSampleRate { get; }
|
||||
|
||||
public UpsamplerState UpsamplerInfo { get; }
|
||||
|
||||
public Memory<float> OutBuffer { get; }
|
||||
|
||||
public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span<byte> inputBufferOffset, uint bufferCount, uint sampleCount, uint sampleRate, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = 0;
|
||||
OutBuffer = info.OutputBuffer;
|
||||
BufferCount = bufferCount;
|
||||
InputSampleCount = sampleCount;
|
||||
InputSampleRate = sampleRate;
|
||||
info.SourceSampleCount = inputCount;
|
||||
info.InputBufferIndices = new ushort[inputCount];
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
|
||||
}
|
||||
|
||||
UpsamplerInfo = info;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Span<float> GetBuffer(int index, int sampleCount)
|
||||
{
|
||||
return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount);
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
float ratio = (float)InputSampleRate / Constants.TargetSampleRate;
|
||||
|
||||
uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
|
||||
|
||||
for (int i = 0; i < bufferCount; i++)
|
||||
{
|
||||
Span<float> inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
|
||||
Span<float> outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
|
||||
|
||||
float fraction = 0.0f;
|
||||
|
||||
ResamplerHelper.ResampleForUpsampler(outputBuffer, inputBuffer, ratio, ref fraction, (int)(InputSampleCount / ratio));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
125
Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
Normal file
125
Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
Normal file
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// 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;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class VolumeCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.Volume;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public float Volume { get; }
|
||||
|
||||
public VolumeCommand(float volume, uint bufferIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)bufferIndex;
|
||||
OutputBufferIndex = (ushort)bufferIndex;
|
||||
|
||||
Volume = volume;
|
||||
}
|
||||
|
||||
private void ProcessVolumeAvx(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
|
||||
{
|
||||
Vector256<float> volumeVec = Vector256.Create(Volume);
|
||||
|
||||
ReadOnlySpan<Vector256<float>> inputVec = MemoryMarshal.Cast<float, Vector256<float>>(inputBuffer);
|
||||
Span<Vector256<float>> outputVec = MemoryMarshal.Cast<float, Vector256<float>>(outputBuffer);
|
||||
|
||||
int sisdStart = inputVec.Length * 8;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec));
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < inputBuffer.Length; i++)
|
||||
{
|
||||
outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessVolumeSse41(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
|
||||
{
|
||||
Vector128<float> volumeVec = Vector128.Create(Volume);
|
||||
|
||||
ReadOnlySpan<Vector128<float>> inputVec = MemoryMarshal.Cast<float, Vector128<float>>(inputBuffer);
|
||||
Span<Vector128<float>> outputVec = MemoryMarshal.Cast<float, Vector128<float>>(outputBuffer);
|
||||
|
||||
int sisdStart = inputVec.Length * 4;
|
||||
|
||||
for (int i = 0; i < inputVec.Length; i++)
|
||||
{
|
||||
outputVec[i] = Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec));
|
||||
}
|
||||
|
||||
for (int i = sisdStart; i < inputBuffer.Length; i++)
|
||||
{
|
||||
outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessVolume(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
|
||||
{
|
||||
if (Avx.IsSupported)
|
||||
{
|
||||
ProcessVolumeAvx(outputBuffer, inputBuffer);
|
||||
}
|
||||
else if (Sse41.IsSupported)
|
||||
{
|
||||
ProcessVolumeSse41(outputBuffer, inputBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessVolumeSlowPath(outputBuffer, inputBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ProcessVolumeSlowPath(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer)
|
||||
{
|
||||
for (int i = 0; i < outputBuffer.Length; i++)
|
||||
{
|
||||
outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
ProcessVolume(outputBuffer, inputBuffer);
|
||||
}
|
||||
}
|
||||
}
|
71
Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs
Normal file
71
Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
public class VolumeRampCommand : ICommand
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public int NodeId { get; }
|
||||
|
||||
public CommandType CommandType => CommandType.VolumeRamp;
|
||||
|
||||
public ulong EstimatedProcessingTime { get; set; }
|
||||
|
||||
public ushort InputBufferIndex { get; }
|
||||
public ushort OutputBufferIndex { get; }
|
||||
|
||||
public float Volume0 { get; }
|
||||
public float Volume1 { get; }
|
||||
|
||||
public VolumeRampCommand(float volume0, float volume1, uint bufferIndex, int nodeId)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
||||
InputBufferIndex = (ushort)bufferIndex;
|
||||
OutputBufferIndex = (ushort)bufferIndex;
|
||||
|
||||
Volume0 = volume0;
|
||||
Volume1 = volume1;
|
||||
}
|
||||
|
||||
private void ProcessVolumeRamp(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, int sampleCount)
|
||||
{
|
||||
float ramp = (Volume1 - Volume0) / sampleCount;
|
||||
|
||||
float volume = Volume0;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
|
||||
volume += ramp;
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(CommandList context)
|
||||
{
|
||||
ReadOnlySpan<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||
Span<float> outputBuffer = context.GetBuffer(OutputBufferIndex);
|
||||
|
||||
ProcessVolumeRamp(outputBuffer, inputBuffer, (int)context.SampleCount);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue