715 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			715 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| //
 | |
| // 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);
 | |
|         }
 | |
|     }
 | |
| }
 | 
