diff options
| author | Mary <me@thog.eu> | 2021-02-26 01:11:56 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-26 01:11:56 +0100 |
| commit | f556c80d0230056335632b60c71f1567e177239e (patch) | |
| tree | 748aa6be62b93a8e941e25dbd83f39e1dbb37035 /Ryujinx.Audio.Renderer/Server/Voice | |
| parent | 1c49089ff00fc87dc4872f135dc6a0d36169a970 (diff) | |
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
Diffstat (limited to 'Ryujinx.Audio.Renderer/Server/Voice')
| -rw-r--r-- | Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs | 57 | ||||
| -rw-r--r-- | Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs | 166 | ||||
| -rw-r--r-- | Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs | 715 | ||||
| -rw-r--r-- | Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs | 121 |
4 files changed, 0 insertions, 1059 deletions
diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs b/Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs deleted file mode 100644 index 00520cfc..00000000 --- a/Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2019-2021 Ryujinx -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. -// - -using Ryujinx.Common.Memory; -using System.Runtime.InteropServices; - -namespace Ryujinx.Audio.Renderer.Server.Voice -{ - /// <summary> - /// Server state for a voice channel resource. - /// </summary> - [StructLayout(LayoutKind.Sequential, Size = 0xD0, Pack = Alignment)] - public struct VoiceChannelResource - { - public const int Alignment = 0x10; - - /// <summary> - /// Mix volumes for the resource. - /// </summary> - public Array24<float> Mix; - - /// <summary> - /// Previous mix volumes for resource. - /// </summary> - public Array24<float> PreviousMix; - - /// <summary> - /// The id of the resource. - /// </summary> - public uint Id; - - /// <summary> - /// Indicate if the resource is used. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool IsUsed; - - public void UpdateState() - { - Mix.ToSpan().CopyTo(PreviousMix.ToSpan()); - } - } -} diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs b/Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs deleted file mode 100644 index 0dfaa4d5..00000000 --- a/Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs +++ /dev/null @@ -1,166 +0,0 @@ -// -// Copyright (c) 2019-2021 Ryujinx -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. -// - -using Ryujinx.Audio.Renderer.Common; -using Ryujinx.Audio.Renderer.Utils; -using System; -using System.Diagnostics; - -namespace Ryujinx.Audio.Renderer.Server.Voice -{ - /// <summary> - /// Voice context. - /// </summary> - public class VoiceContext - { - /// <summary> - /// Storage of the sorted indices to <see cref="VoiceState"/>. - /// </summary> - private Memory<int> _sortedVoices; - - /// <summary> - /// Storage for <see cref="VoiceState"/>. - /// </summary> - private Memory<VoiceState> _voices; - - /// <summary> - /// Storage for <see cref="VoiceChannelResource"/>. - /// </summary> - private Memory<VoiceChannelResource> _voiceChannelResources; - - /// <summary> - /// Storage for <see cref="VoiceUpdateState"/> that are used during audio renderer server updates. - /// </summary> - private Memory<VoiceUpdateState> _voiceUpdateStatesCpu; - - /// <summary> - /// Storage for <see cref="VoiceUpdateState"/> for the <see cref="Dsp.AudioProcessor"/>. - /// </summary> - private Memory<VoiceUpdateState> _voiceUpdateStatesDsp; - - /// <summary> - /// The total voice count. - /// </summary> - private uint _voiceCount; - - public void Initialize(Memory<int> sortedVoices, Memory<VoiceState> voices, Memory<VoiceChannelResource> voiceChannelResources, Memory<VoiceUpdateState> voiceUpdateStatesCpu, Memory<VoiceUpdateState> voiceUpdateStatesDsp, uint voiceCount) - { - _sortedVoices = sortedVoices; - _voices = voices; - _voiceChannelResources = voiceChannelResources; - _voiceUpdateStatesCpu = voiceUpdateStatesCpu; - _voiceUpdateStatesDsp = voiceUpdateStatesDsp; - _voiceCount = voiceCount; - } - - /// <summary> - /// Get the total voice count. - /// </summary> - /// <returns>The total voice count.</returns> - public uint GetCount() - { - return _voiceCount; - } - - /// <summary> - /// Get a reference to a <see cref="VoiceChannelResource"/> at the given <paramref name="id"/>. - /// </summary> - /// <param name="id">The index to use.</param> - /// <returns>A reference to a <see cref="VoiceChannelResource"/> at the given <paramref name="id"/>.</returns> - public ref VoiceChannelResource GetChannelResource(int id) - { - return ref SpanIOHelper.GetFromMemory(_voiceChannelResources, id, _voiceCount); - } - - /// <summary> - /// Get a <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>. - /// </summary> - /// <param name="id">The index to use.</param> - /// <returns>A <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.</returns> - /// <remarks>The returned <see cref="Memory{VoiceUpdateState}"/> should only be used when updating the server state.</remarks> - public Memory<VoiceUpdateState> GetUpdateStateForCpu(int id) - { - return SpanIOHelper.GetMemory(_voiceUpdateStatesCpu, id, _voiceCount); - } - - /// <summary> - /// Get a <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>. - /// </summary> - /// <param name="id">The index to use.</param> - /// <returns>A <see cref="Memory{VoiceUpdateState}"/> at the given <paramref name="id"/>.</returns> - /// <remarks>The returned <see cref="Memory{VoiceUpdateState}"/> should only be used in the context of processing on the <see cref="Dsp.AudioProcessor"/>.</remarks> - public Memory<VoiceUpdateState> GetUpdateStateForDsp(int id) - { - return SpanIOHelper.GetMemory(_voiceUpdateStatesDsp, id, _voiceCount); - } - - /// <summary> - /// Get a reference to a <see cref="VoiceState"/> at the given <paramref name="id"/>. - /// </summary> - /// <param name="id">The index to use.</param> - /// <returns>A reference to a <see cref="VoiceState"/> at the given <paramref name="id"/>.</returns> - public ref VoiceState GetState(int id) - { - return ref SpanIOHelper.GetFromMemory(_voices, id, _voiceCount); - } - - public ref VoiceState GetSortedState(int id) - { - Debug.Assert(id >= 0 && id < _voiceCount); - - return ref GetState(_sortedVoices.Span[id]); - } - - /// <summary> - /// Update internal state during command generation. - /// </summary> - public void UpdateForCommandGeneration() - { - _voiceUpdateStatesDsp.CopyTo(_voiceUpdateStatesCpu); - } - - /// <summary> - /// Sort the internal voices by priority and sorting order (if the priorities match). - /// </summary> - public void Sort() - { - for (int i = 0; i < _voiceCount; i++) - { - _sortedVoices.Span[i] = i; - } - - int[] sortedVoicesTemp = _sortedVoices.Slice(0, (int)GetCount()).ToArray(); - - Array.Sort(sortedVoicesTemp, (a, b) => - { - ref VoiceState aState = ref GetState(a); - ref VoiceState bState = ref GetState(b); - - int result = aState.Priority.CompareTo(bState.Priority); - - if (result == 0) - { - return aState.SortingOrder.CompareTo(bState.SortingOrder); - } - - return result; - }); - - sortedVoicesTemp.AsSpan().CopyTo(_sortedVoices.Span); - } - } -} diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs b/Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs deleted file mode 100644 index f30ee2f7..00000000 --- a/Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs +++ /dev/null @@ -1,715 +0,0 @@ -// -// Copyright (c) 2019-2021 Ryujinx -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. -// - -using Ryujinx.Audio.Renderer.Common; -using Ryujinx.Audio.Renderer.Parameter; -using Ryujinx.Audio.Renderer.Server.MemoryPool; -using Ryujinx.Common.Memory; -using Ryujinx.Common.Utilities; -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; -using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; - -namespace Ryujinx.Audio.Renderer.Server.Voice -{ - [StructLayout(LayoutKind.Sequential, Pack = Alignment)] - public struct VoiceState - { - public const int Alignment = 0x10; - - /// <summary> - /// Set to true if the voice is used. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool InUse; - - /// <summary> - /// Set to true if the voice is new. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool IsNew; - - [MarshalAs(UnmanagedType.I1)] - public bool WasPlaying; - - /// <summary> - /// The <see cref="SampleFormat"/> of the voice. - /// </summary> - public SampleFormat SampleFormat; - - /// <summary> - /// The sample rate of the voice. - /// </summary> - public uint SampleRate; - - /// <summary> - /// The total channel count used. - /// </summary> - public uint ChannelsCount; - - /// <summary> - /// Id of the voice. - /// </summary> - public int Id; - - /// <summary> - /// Node id of the voice. - /// </summary> - public int NodeId; - - /// <summary> - /// The target mix id of the voice. - /// </summary> - public int MixId; - - /// <summary> - /// The current voice <see cref="Types.PlayState"/>. - /// </summary> - public Types.PlayState PlayState; - - /// <summary> - /// The previous voice <see cref="Types.PlayState"/>. - /// </summary> - public Types.PlayState PreviousPlayState; - - /// <summary> - /// The priority of the voice. - /// </summary> - public uint Priority; - - /// <summary> - /// Target sorting position of the voice. (used to sort voice with the same <see cref="Priority"/>) - /// </summary> - public uint SortingOrder; - - /// <summary> - /// The pitch used on the voice. - /// </summary> - public float Pitch; - - /// <summary> - /// The output volume of the voice. - /// </summary> - public float Volume; - - /// <summary> - /// The previous output volume of the voice. - /// </summary> - public float PreviousVolume; - - /// <summary> - /// Biquad filters to apply to the output of the voice. - /// </summary> - public Array2<BiquadFilterParameter> BiquadFilters; - - /// <summary> - /// Total count of <see cref="WaveBufferInternal"/> of the voice. - /// </summary> - public uint WaveBuffersCount; - - /// <summary> - /// Current playing <see cref="WaveBufferInternal"/> of the voice. - /// </summary> - public uint WaveBuffersIndex; - - /// <summary> - /// Change the behaviour of the voice. - /// </summary> - /// <remarks>This was added on REV5.</remarks> - public DecodingBehaviour DecodingBehaviour; - - /// <summary> - /// User state <see cref="AddressInfo"/> required by the data source. - /// </summary> - /// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the GC-ADPCM coefficients.</remarks> - public AddressInfo DataSourceStateAddressInfo; - - /// <summary> - /// The wavebuffers of this voice. - /// </summary> - public Array4<WaveBuffer> WaveBuffers; - - /// <summary> - /// The channel resource ids associated to the voice. - /// </summary> - public Array6<int> ChannelResourceIds; - - /// <summary> - /// The target splitter id of the voice. - /// </summary> - public uint SplitterId; - - /// <summary> - /// Change the Sample Rate Conversion (SRC) quality of the voice. - /// </summary> - /// <remarks>This was added on REV8.</remarks> - public SampleRateConversionQuality SrcQuality; - - /// <summary> - /// If set to true, the voice was dropped. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool VoiceDropFlag; - - /// <summary> - /// Set to true if the data source state work buffer wasn't mapped. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool DataSourceStateUnmapped; - - /// <summary> - /// Set to true if any of the <see cref="WaveBuffer.BufferAddressInfo"/> work buffer wasn't mapped. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool BufferInfoUnmapped; - - /// <summary> - /// The biquad filter initialization state storage. - /// </summary> - private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization; - - /// <summary> - /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. - /// </summary> - /// <remarks>This was added on REV5.</remarks> - public byte FlushWaveBufferCount; - - [StructLayout(LayoutKind.Sequential, Size = RendererConstants.VoiceBiquadFilterCount)] - private struct BiquadFilterNeedInitializationArrayStruct { } - - /// <summary> - /// The biquad filter initialization state array. - /// </summary> - public Span<bool> BiquadFilterNeedInitialization => SpanHelpers.AsSpan<BiquadFilterNeedInitializationArrayStruct, bool>(ref _biquadFilterNeedInitialization); - - /// <summary> - /// Initialize the <see cref="VoiceState"/>. - /// </summary> - public void Initialize() - { - IsNew = false; - VoiceDropFlag = false; - DataSourceStateUnmapped = false; - BufferInfoUnmapped = false; - FlushWaveBufferCount = 0; - PlayState = Types.PlayState.Stopped; - Priority = RendererConstants.VoiceLowestPriority; - Id = 0; - NodeId = 0; - SampleRate = 0; - SampleFormat = SampleFormat.Invalid; - ChannelsCount = 0; - Pitch = 0.0f; - Volume= 0.0f; - PreviousVolume = 0.0f; - BiquadFilters.ToSpan().Fill(new BiquadFilterParameter()); - WaveBuffersCount = 0; - WaveBuffersIndex = 0; - MixId = RendererConstants.UnusedMixId; - SplitterId = RendererConstants.UnusedSplitterId; - DataSourceStateAddressInfo.Setup(0, 0); - - InitializeWaveBuffers(); - } - - /// <summary> - /// Initialize the <see cref="WaveBuffer"/> in this <see cref="VoiceState"/>. - /// </summary> - private void InitializeWaveBuffers() - { - for (int i = 0; i < WaveBuffers.Length; i++) - { - WaveBuffers[i].StartSampleOffset = 0; - WaveBuffers[i].EndSampleOffset = 0; - WaveBuffers[i].ShouldLoop = false; - WaveBuffers[i].IsEndOfStream = false; - WaveBuffers[i].BufferAddressInfo.Setup(0, 0); - WaveBuffers[i].ContextAddressInfo.Setup(0, 0); - WaveBuffers[i].IsSendToAudioProcessor = true; - } - } - - /// <summary> - /// Check if the voice needs to be skipped. - /// </summary> - /// <returns>Returns true if the voice needs to be skipped.</returns> - public bool ShouldSkip() - { - return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag; - } - - /// <summary> - /// Return true if the mix has any destinations. - /// </summary> - /// <returns>True if the mix has any destinations.</returns> - public bool HasAnyDestination() - { - return MixId != RendererConstants.UnusedMixId || SplitterId != RendererConstants.UnusedSplitterId; - } - - /// <summary> - /// Indicate if the server voice information needs to be updated. - /// </summary> - /// <param name="parameter">The user parameter.</param> - /// <returns>Return true, if the server voice information needs to be updated.</returns> - private bool ShouldUpdateParameters(ref VoiceInParameter parameter) - { - if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) - { - return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize; - } - - return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress || - DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize || - DataSourceStateUnmapped; - } - - /// <summary> - /// Update the internal state from a user parameter. - /// </summary> - /// <param name="outErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param> - /// <param name="parameter">The user parameter.</param> - /// <param name="poolMapper">The mapper to use.</param> - /// <param name="behaviourContext">The behaviour context.</param> - public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext) - { - InUse = parameter.InUse; - Id = parameter.Id; - NodeId = parameter.NodeId; - - UpdatePlayState(parameter.PlayState); - - SrcQuality = parameter.SrcQuality; - - Priority = parameter.Priority; - SortingOrder = parameter.SortingOrder; - SampleRate = parameter.SampleRate; - SampleFormat = parameter.SampleFormat; - ChannelsCount = parameter.ChannelCount; - Pitch = parameter.Pitch; - Volume = parameter.Volume; - parameter.BiquadFilters.ToSpan().CopyTo(BiquadFilters.ToSpan()); - WaveBuffersCount = parameter.WaveBuffersCount; - WaveBuffersIndex = parameter.WaveBuffersIndex; - - if (behaviourContext.IsFlushVoiceWaveBuffersSupported()) - { - FlushWaveBufferCount += parameter.FlushWaveBufferCount; - } - - MixId = parameter.MixId; - - if (behaviourContext.IsSplitterSupported()) - { - SplitterId = parameter.SplitterId; - } - else - { - SplitterId = RendererConstants.UnusedSplitterId; - } - - parameter.ChannelResourceIds.ToSpan().CopyTo(ChannelResourceIds.ToSpan()); - - DecodingBehaviour behaviour = DecodingBehaviour.Default; - - if (behaviourContext.IsDecodingBehaviourFlagSupported()) - { - behaviour = parameter.DecodingBehaviourFlags; - } - - DecodingBehaviour = behaviour; - - if (parameter.ResetVoiceDropFlag) - { - VoiceDropFlag = false; - } - - if (ShouldUpdateParameters(ref parameter)) - { - DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); - } - else - { - outErrorInfo = new ErrorInfo(); - } - } - - /// <summary> - /// Update the internal play state from user play state. - /// </summary> - /// <param name="userPlayState">The target user play state.</param> - public void UpdatePlayState(PlayState userPlayState) - { - Types.PlayState oldServerPlayState = PlayState; - - PreviousPlayState = oldServerPlayState; - - Types.PlayState newServerPlayState; - - switch (userPlayState) - { - case Common.PlayState.Start: - newServerPlayState = Types.PlayState.Started; - break; - - case Common.PlayState.Stop: - if (oldServerPlayState == Types.PlayState.Stopped) - { - return; - } - - newServerPlayState = Types.PlayState.Stopping; - break; - - case Common.PlayState.Pause: - newServerPlayState = Types.PlayState.Paused; - break; - - default: - throw new NotImplementedException($"Unhandled PlayState.{userPlayState}"); - } - - PlayState = newServerPlayState; - } - - /// <summary> - /// Write the status of the voice to the given user output. - /// </summary> - /// <param name="outStatus">The given user output.</param> - /// <param name="parameter">The user parameter.</param> - /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param> - public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates) - { -#if DEBUG - // Sanity check in debug mode of the internal state - if (!parameter.IsNew && !IsNew) - { - for (int i = 1; i < ChannelsCount; i++) - { - ref VoiceUpdateState stateA = ref voiceUpdateStates[i - 1].Span[0]; - ref VoiceUpdateState stateB = ref voiceUpdateStates[i].Span[0]; - - Debug.Assert(stateA.WaveBufferConsumed == stateB.WaveBufferConsumed); - Debug.Assert(stateA.PlayedSampleCount == stateB.PlayedSampleCount); - Debug.Assert(stateA.Offset == stateB.Offset); - Debug.Assert(stateA.WaveBufferIndex == stateB.WaveBufferIndex); - Debug.Assert(stateA.Fraction == stateB.Fraction); - Debug.Assert(stateA.IsWaveBufferValid.SequenceEqual(stateB.IsWaveBufferValid)); - } - } -#endif - if (parameter.IsNew || IsNew) - { - IsNew = true; - - outStatus.VoiceDropFlag = false; - outStatus.PlayedWaveBuffersCount = 0; - outStatus.PlayedSampleCount = 0; - } - else - { - ref VoiceUpdateState state = ref voiceUpdateStates[0].Span[0]; - - outStatus.VoiceDropFlag = VoiceDropFlag; - outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed; - outStatus.PlayedSampleCount = state.PlayedSampleCount; - } - } - - /// <summary> - /// Update the internal state of all the <see cref="WaveBuffer"/> of the <see cref="VoiceState"/>. - /// </summary> - /// <param name="errorInfos">An array of <see cref="ErrorInfo"/> used to report errors when mapping any of the <see cref="WaveBuffer"/>.</param> - /// <param name="parameter">The user parameter.</param> - /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param> - /// <param name="mapper">The mapper to use.</param> - /// <param name="behaviourContext">The behaviour context.</param> - public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) - { - errorInfos = new ErrorInfo[RendererConstants.VoiceWaveBufferCount * 2]; - - if (parameter.IsNew) - { - InitializeWaveBuffers(); - - for (int i = 0; i < parameter.ChannelCount; i++) - { - voiceUpdateStates[i].Span[0].IsWaveBufferValid.Fill(false); - } - } - - ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0]; - - for (int i = 0; i < RendererConstants.VoiceWaveBufferCount; i++) - { - UpdateWaveBuffer(errorInfos.AsSpan().Slice(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); - } - } - - /// <summary> - /// Update the internal state of one of the <see cref="WaveBuffer"/> of the <see cref="VoiceState"/>. - /// </summary> - /// <param name="errorInfos">A <see cref="Span{ErrorInfo}"/> used to report errors when mapping the <see cref="WaveBuffer"/>.</param> - /// <param name="waveBuffer">The <see cref="WaveBuffer"/> to update.</param> - /// <param name="inputWaveBuffer">The <see cref="WaveBufferInternal"/> from the user input.</param> - /// <param name="sampleFormat">The <see cref="SampleFormat"/> from the user input.</param> - /// <param name="isValid">If set to true, the server side wavebuffer is considered valid.</param> - /// <param name="mapper">The mapper to use.</param> - /// <param name="behaviourContext">The behaviour context.</param> - private void UpdateWaveBuffer(Span<ErrorInfo> errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext) - { - if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0) - { - mapper.ForceUnmap(ref waveBuffer.BufferAddressInfo); - waveBuffer.BufferAddressInfo.Setup(0, 0); - } - - if (!inputWaveBuffer.SentToServer || BufferInfoUnmapped) - { - if (inputWaveBuffer.IsSampleOffsetValid(sampleFormat)) - { - Debug.Assert(waveBuffer.IsSendToAudioProcessor); - - waveBuffer.IsSendToAudioProcessor = false; - waveBuffer.StartSampleOffset = inputWaveBuffer.StartSampleOffset; - waveBuffer.EndSampleOffset = inputWaveBuffer.EndSampleOffset; - waveBuffer.ShouldLoop = inputWaveBuffer.ShouldLoop; - waveBuffer.IsEndOfStream = inputWaveBuffer.IsEndOfStream; - waveBuffer.LoopStartSampleOffset = inputWaveBuffer.LoopFirstSampleOffset; - waveBuffer.LoopEndSampleOffset = inputWaveBuffer.LoopLastSampleOffset; - waveBuffer.LoopCount = inputWaveBuffer.LoopCount; - - BufferInfoUnmapped = !mapper.TryAttachBuffer(out ErrorInfo bufferInfoError, ref waveBuffer.BufferAddressInfo, inputWaveBuffer.Address, inputWaveBuffer.Size); - - errorInfos[0] = bufferInfoError; - - if (sampleFormat == SampleFormat.Adpcm && behaviourContext.IsAdpcmLoopContextBugFixed() && inputWaveBuffer.ContextAddress != 0) - { - bool adpcmLoopContextMapped = mapper.TryAttachBuffer(out ErrorInfo adpcmLoopContextInfoError, - ref waveBuffer.ContextAddressInfo, - inputWaveBuffer.ContextAddress, - inputWaveBuffer.ContextSize); - - errorInfos[1] = adpcmLoopContextInfoError; - - if (adpcmLoopContextMapped) - { - BufferInfoUnmapped = DataSourceStateUnmapped; - } - else - { - BufferInfoUnmapped = true; - } - } - else - { - waveBuffer.ContextAddressInfo.Setup(0, 0); - } - } - else - { - errorInfos[0].ErrorCode = ResultCode.InvalidAddressInfo; - errorInfos[0].ExtraErrorInfo = inputWaveBuffer.Address; - } - } - } - - /// <summary> - /// Reset the resources associated to this <see cref="VoiceState"/>. - /// </summary> - /// <param name="context">The voice context.</param> - private void ResetResources(VoiceContext context) - { - for (int i = 0; i < ChannelsCount; i++) - { - int channelResourceId = ChannelResourceIds[i]; - - ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId); - - Debug.Assert(voiceChannelResource.IsUsed); - - Memory<VoiceUpdateState> dspSharedState = context.GetUpdateStateForDsp(channelResourceId); - - MemoryMarshal.Cast<VoiceUpdateState, byte>(dspSharedState.Span).Fill(0); - - voiceChannelResource.UpdateState(); - } - } - - /// <summary> - /// Flush a certain amount of <see cref="WaveBuffer"/>. - /// </summary> - /// <param name="waveBufferCount">The amount of wavebuffer to flush.</param> - /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param> - /// <param name="channelCount">The channel count from user input.</param> - private void FlushWaveBuffers(uint waveBufferCount, Memory<VoiceUpdateState>[] voiceUpdateStates, uint channelCount) - { - uint waveBufferIndex = WaveBuffersIndex; - - for (int i = 0; i < waveBufferCount; i++) - { - WaveBuffers[(int)waveBufferIndex].IsSendToAudioProcessor = true; - - for (int j = 0; j < channelCount; j++) - { - ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; - - voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; - voiceUpdateState.WaveBufferConsumed++; - voiceUpdateState.IsWaveBufferValid[(int)waveBufferIndex] = false; - } - - waveBufferIndex = (waveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; - } - } - - /// <summary> - /// Update the internal parameters for command generation. - /// </summary> - /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param> - /// <returns>Return true if this voice should be played.</returns> - public bool UpdateParametersForCommandGeneration(Memory<VoiceUpdateState>[] voiceUpdateStates) - { - if (FlushWaveBufferCount != 0) - { - FlushWaveBuffers(FlushWaveBufferCount, voiceUpdateStates, ChannelsCount); - - FlushWaveBufferCount = 0; - } - - switch (PlayState) - { - case Types.PlayState.Started: - for (int i = 0; i < WaveBuffers.Length; i++) - { - ref WaveBuffer wavebuffer = ref WaveBuffers[i]; - - if (!wavebuffer.IsSendToAudioProcessor) - { - for (int y = 0; y < ChannelsCount; y++) - { - Debug.Assert(!voiceUpdateStates[y].Span[0].IsWaveBufferValid[i]); - - voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true; - } - - wavebuffer.IsSendToAudioProcessor = true; - } - } - - WasPlaying = false; - - ref VoiceUpdateState primaryVoiceUpdateState = ref voiceUpdateStates[0].Span[0]; - - for (int i = 0; i < primaryVoiceUpdateState.IsWaveBufferValid.Length; i++) - { - if (primaryVoiceUpdateState.IsWaveBufferValid[i]) - { - return true; - } - } - - return false; - - case Types.PlayState.Stopping: - for (int i = 0; i < WaveBuffers.Length; i++) - { - ref WaveBuffer wavebuffer = ref WaveBuffers[i]; - - wavebuffer.IsSendToAudioProcessor = true; - - for (int j = 0; j < ChannelsCount; j++) - { - ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; - - if (voiceUpdateState.IsWaveBufferValid[i]) - { - voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; - voiceUpdateState.WaveBufferConsumed++; - } - - voiceUpdateState.IsWaveBufferValid[i] = false; - } - } - - for (int i = 0; i < ChannelsCount; i++) - { - ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[i].Span[0]; - - voiceUpdateState.Offset = 0; - voiceUpdateState.PlayedSampleCount = 0; - voiceUpdateState.Pitch.ToSpan().Fill(0); - voiceUpdateState.Fraction = 0; - voiceUpdateState.LoopContext = new Dsp.State.AdpcmLoopContext(); - } - - PlayState = Types.PlayState.Stopped; - WasPlaying = PreviousPlayState == Types.PlayState.Started; - - return WasPlaying; - - case Types.PlayState.Stopped: - case Types.PlayState.Paused: - foreach (ref WaveBuffer wavebuffer in WaveBuffers.ToSpan()) - { - wavebuffer.BufferAddressInfo.GetReference(true); - wavebuffer.ContextAddressInfo.GetReference(true); - } - - if (SampleFormat == SampleFormat.Adpcm) - { - if (DataSourceStateAddressInfo.CpuAddress != 0) - { - DataSourceStateAddressInfo.GetReference(true); - } - } - - WasPlaying = PreviousPlayState == Types.PlayState.Started; - - return WasPlaying; - default: - throw new NotImplementedException($"{PlayState}"); - } - } - - /// <summary> - /// Update the internal state for command generation. - /// </summary> - /// <param name="context">The voice context.</param> - /// <returns>Return true if this voice should be played.</returns> - public bool UpdateForCommandGeneration(VoiceContext context) - { - if (IsNew) - { - ResetResources(context); - PreviousVolume = Volume; - IsNew = false; - } - - Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[RendererConstants.VoiceChannelCountMax]; - - for (int i = 0; i < ChannelsCount; i++) - { - voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]); - } - - return UpdateParametersForCommandGeneration(voiceUpdateStates); - } - } -} diff --git a/Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs b/Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs deleted file mode 100644 index a03aa757..00000000 --- a/Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs +++ /dev/null @@ -1,121 +0,0 @@ -// -// 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.MemoryPool; -using System.Runtime.InteropServices; - -namespace Ryujinx.Audio.Renderer.Server.Voice -{ - /// <summary> - /// A wavebuffer used for server update. - /// </summary> - [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 1)] - public struct WaveBuffer - { - /// <summary> - /// The <see cref="AddressInfo"/> of the sample data of the wavebuffer. - /// </summary> - public AddressInfo BufferAddressInfo; - - /// <summary> - /// The <see cref="AddressInfo"/> of the context of the wavebuffer. - /// </summary> - /// <remarks>Only used by <see cref="Common.SampleFormat.Adpcm"/>.</remarks> - public AddressInfo ContextAddressInfo; - - - /// <summary> - /// First sample to play of the wavebuffer. - /// </summary> - public uint StartSampleOffset; - - /// <summary> - /// Last sample to play of the wavebuffer. - /// </summary> - public uint EndSampleOffset; - - /// <summary> - /// Set to true if the wavebuffer is looping. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool ShouldLoop; - - /// <summary> - /// Set to true if the wavebuffer is the end of stream. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool IsEndOfStream; - - /// <summary> - /// Set to true if the wavebuffer wasn't sent to the <see cref="Dsp.AudioProcessor"/>. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool IsSendToAudioProcessor; - - /// <summary> - /// First sample to play when looping the wavebuffer. - /// </summary> - public uint LoopStartSampleOffset; - - /// <summary> - /// Last sample to play when looping the wavebuffer. - /// </summary> - public uint LoopEndSampleOffset; - - /// <summary> - /// The max loop count. - /// </summary> - public int LoopCount; - - /// <summary> - /// Create a new <see cref="Common.WaveBuffer"/> for use by the <see cref="Dsp.AudioProcessor"/>. - /// </summary> - /// <param name="version">The target version of the wavebuffer.</param> - /// <returns>A new <see cref="Common.WaveBuffer"/> for use by the <see cref="Dsp.AudioProcessor"/>.</returns> - public Common.WaveBuffer ToCommon(int version) - { - Common.WaveBuffer waveBuffer = new Common.WaveBuffer(); - - waveBuffer.Buffer = BufferAddressInfo.GetReference(true); - waveBuffer.BufferSize = (uint)BufferAddressInfo.Size; - - if (ContextAddressInfo.CpuAddress != 0) - { - waveBuffer.Context = ContextAddressInfo.GetReference(true); - waveBuffer.ContextSize = (uint)ContextAddressInfo.Size; - } - - waveBuffer.StartSampleOffset = StartSampleOffset; - waveBuffer.EndSampleOffset = EndSampleOffset; - waveBuffer.Looping = ShouldLoop; - waveBuffer.IsEndOfStream = IsEndOfStream; - - if (version == 2) - { - waveBuffer.LoopCount = LoopCount; - waveBuffer.LoopStartSampleOffset = LoopStartSampleOffset; - waveBuffer.LoopEndSampleOffset = LoopEndSampleOffset; - } - else - { - waveBuffer.LoopCount = -1; - } - - return waveBuffer; - } - } -} |
