From a389dd59bd881cf2cff09a1f67f5c30de61123e6 Mon Sep 17 00:00:00 2001 From: Mary Date: Tue, 18 Aug 2020 03:49:37 +0200 Subject: Amadeus: Final Act (#1481) * Amadeus: Final Act This is my requiem, I present to you Amadeus, a complete reimplementation of the Audio Renderer! This reimplementation is based on my reversing of every version of the audio system module that I carried for the past 10 months. This supports every revision (at the time of writing REV1 to REV8 included) and all features proposed by the Audio Renderer on real hardware. Because this component could be used outside an emulation context, and to avoid possible "inspirations" not crediting the project, I decided to license the Ryujinx.Audio.Renderer project under LGPLv3. - FE3H voices in videos and chapter intro are not present. - Games that use two audio renderer **at the same time** are probably going to have issues right now **until we rewrite the audio output interface** (Crash Team Racing is the only known game to use two renderer at the same time). - Persona 5 Scrambler now goes ingame but audio is garbage. This is caused by the fact that the game engine is syncing audio and video in a really aggressive way. This will disappears the day this game run at full speed. * Make timing more precise when sleeping on Windows Improve precision to a 1ms resolution on Windows NT based OS. This is used to avoid having totally erratic timings and unify all Windows users to the same resolution. NOTE: This is only active when emulation is running. --- Ryujinx.Audio.Renderer/Server/CommandBuffer.cs | 484 +++++++++++++++++++++++++ 1 file changed, 484 insertions(+) create mode 100644 Ryujinx.Audio.Renderer/Server/CommandBuffer.cs (limited to 'Ryujinx.Audio.Renderer/Server/CommandBuffer.cs') diff --git a/Ryujinx.Audio.Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio.Renderer/Server/CommandBuffer.cs new file mode 100644 index 00000000..c5e6d10a --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/CommandBuffer.cs @@ -0,0 +1,484 @@ +// +// Copyright (c) 2019-2020 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 . +// + +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// An API to generate commands and aggregate them into a . + /// + public class CommandBuffer + { + /// + /// The command processing time estimator in use. + /// + private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + /// + /// The estimated total processing time. + /// + public ulong EstimatedProcessingTime { get; set; } + + /// + /// The command list that is populated by the . + /// + public CommandList CommandList { get; } + + /// + /// Create a new . + /// + /// The command list that will store the generated commands. + /// The command processing time estimator to use. + public CommandBuffer(CommandList commandList, ICommandProcessingTimeEstimator commandProcessingTimeEstimator) + { + CommandList = commandList; + EstimatedProcessingTime = 0; + _commandProcessingTimeEstimator = commandProcessingTimeEstimator; + } + + /// + /// Add a new generated command to the . + /// + /// The command to add. + private void AddCommand(ICommand command) + { + EstimatedProcessingTime += command.EstimatedProcessingTime; + + CommandList.AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The node id associated to this command. + public void GenerateClearMixBuffer(int nodeId) + { + ClearMixBufferCommand command = new ClearMixBufferCommand(nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The voice state associated. + /// The depop buffer. + /// The buffer count. + /// The target buffer offset. + /// The node id associated to this command. + /// Set to true if the voice was playing previously. + public void GenerateDepopPrepare(Memory state, Memory depopBuffer, uint bufferCount, uint bufferOffset, int nodeId, bool wasPlaying) + { + DepopPrepareCommand command = new DepopPrepareCommand(state, depopBuffer, bufferCount, bufferOffset, nodeId, wasPlaying); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The . + /// The performance operation to perform. + /// The node id associated to this command. + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + PerformanceCommand command = new PerformanceCommand(ref performanceEntryAddresses, type, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The previous volume. + /// The new volume. + /// The index of the mix buffer to use. + /// The node id associated to this command. + public void GenerateVolumeRamp(float previousVolume, float volume, uint bufferIndex, int nodeId) + { + VolumeRampCommand command = new VolumeRampCommand(previousVolume, volume, bufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GenerateDataSourceVersion2(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + DataSourceVersion2Command command = new DataSourceVersion2Command(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmInt16DataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmInt16DataSourceCommandVersion1 command = new PcmInt16DataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmFloatDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmFloatDataSourceCommandVersion1 command = new PcmFloatDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The node id associated to this command. + public void GenerateAdpcmDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, int nodeId) + { + AdpcmDataSourceCommandVersion1 command = new AdpcmDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The base index of the input and output buffer. + /// The biquad filter parameter. + /// The biquad state. + /// The input buffer offset. + /// The output buffer offset. + /// Set to true if the biquad filter state needs to be initialized. + /// The node id associated to this command. + public void GenerateBiquadFilter(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId) + { + BiquadFilterCommand command = new BiquadFilterCommand(baseIndex, ref filter, biquadFilterStateMemory, inputBufferOffset, outputBufferOffset, needInitialization, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The mix buffer count. + /// The base input index. + /// The base output index. + /// The previous volume. + /// The new volume. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span previousVolume, Span volume, Memory state, int nodeId) + { + MixRampGroupedCommand command = new MixRampGroupedCommand(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRamp(float previousVolume, float volume, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId) + { + MixRampCommand command = new MixRampCommand(previousVolume, volume, inputBufferIndex, outputBufferIndex, lastSampleIndex, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The depop buffer. + /// The target buffer offset. + /// The buffer count. + /// The node id associated to this command. + /// The target sample rate in use. + public void GenerateDepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate) + { + DepopForMixBuffersCommand command = new DepopForMixBuffersCommand(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + public void GenerateCopyMixBuffer(uint inputBufferIndex, uint outputBufferIndex, int nodeId) + { + CopyMixBufferCommand command = new CopyMixBufferCommand(inputBufferIndex, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + /// The mix volume. + public void GenerateMix(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume) + { + MixCommand command = new MixCommand(inputBufferIndex, outputBufferIndex, nodeId, volume); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb parameter. + /// The reverb state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the long size pre-delay is supported. + public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported) + { + if (parameter.IsChannelCountValid()) + { + ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb 3d parameter. + /// The reverb 3d state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The delay parameter. + /// The delay state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The input buffer offset. + /// The output buffer offset. + /// The aux state. + /// Set to true if the effect should be active. + /// The limit of the circular buffer. + /// The guest address of the output buffer. + /// The guest address of the input buffer. + /// The count to add on the offset after write/read operations. + /// The write offset. + /// The node id associated to this command. + public void GenerateAuxEffect(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, ref AuxiliaryBufferAddresses state, bool isEnabled, uint countMax, CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + if (state.SendBufferInfoBase != 0 && state.ReturnBufferInfoBase != 0) + { + AuxiliaryBufferCommand command = new AuxiliaryBufferCommand(bufferOffset, inputBufferOffset, outputBufferOffset, ref state, isEnabled, countMax, outputBuffer, inputBuffer, updateCount, writeOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target volume to apply. + /// The offset of the mix buffer. + /// The node id associated to this command. + public void GenerateVolume(float volume, uint bufferOffset, int nodeId) + { + VolumeCommand command = new VolumeCommand(volume, bufferOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the circular buffer. + /// The node id associated to this command. + public void GenerateCircularBuffer(uint bufferOffset, CircularBufferSink sink, int nodeId) + { + CircularBufferSinkCommand command = new CircularBufferSinkCommand(bufferOffset, ref sink.Parameter, ref sink.CircularBufferAddressInfo, sink.CurrentWriteOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The input buffer offset. + /// The output buffer offset. + /// The downmixer parameters to use. + /// The node id associated to this command. + public void GenerateDownMixSurroundToStereo(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, ReadOnlySpan downMixParameter, int nodeId) + { + DownMixSurroundToStereoCommand command = new DownMixSurroundToStereoCommand(bufferOffset, inputBufferOffset, outputBufferOffset, downMixParameter, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The associated. + /// The total input count. + /// The input buffer mix offset. + /// The buffer count per sample. + /// The source sample count. + /// The source sample rate. + /// The node id associated to this command. + public void GenerateUpsample(uint bufferOffset, UpsamplerState upsampler, uint inputCount, Span inputBufferOffset, uint bufferCountPerSample, uint sampleCount, uint sampleRate, int nodeId) + { + UpsampleCommand command = new UpsampleCommand(bufferOffset, upsampler, inputCount, inputBufferOffset, bufferCountPerSample, sampleCount, sampleRate, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the device sink. + /// The current audio renderer session id. + /// The mix buffer in use. + /// The node id associated to this command. + public void GenerateDeviceSink(uint bufferOffset, DeviceSink sink, int sessionId, Memory buffer, int nodeId) + { + DeviceSinkCommand command = new DeviceSinkCommand(bufferOffset, sink, sessionId, buffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } +} -- cgit v1.2.3