using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Dsp; using Ryujinx.Audio.Renderer.Dsp.State; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Server.MemoryPool; using Ryujinx.Common; using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; using PlayState = Ryujinx.Audio.Renderer.Server.Types.PlayState; namespace Ryujinx.Audio.Renderer.Server.Voice { [StructLayout(LayoutKind.Sequential, Pack = Alignment)] public struct VoiceInfo { public const int Alignment = 0x10; private static readonly ObjectPool[]> voiceStatesPool = new(() => new Memory[Constants.VoiceChannelCountMax]); /// /// Set to true if the voice is used. /// [MarshalAs(UnmanagedType.I1)] public bool InUse; /// /// Set to true if the voice is new. /// [MarshalAs(UnmanagedType.I1)] public bool IsNew; [MarshalAs(UnmanagedType.I1)] public bool WasPlaying; /// /// The of the voice. /// public SampleFormat SampleFormat; /// /// The sample rate of the voice. /// public uint SampleRate; /// /// The total channel count used. /// public uint ChannelsCount; /// /// Id of the voice. /// public int Id; /// /// Node id of the voice. /// public int NodeId; /// /// The target mix id of the voice. /// public int MixId; /// /// The current voice . /// public PlayState PlayState; /// /// The previous voice . /// public PlayState PreviousPlayState; /// /// The priority of the voice. /// public uint Priority; /// /// Target sorting position of the voice. (used to sort voice with the same ) /// public uint SortingOrder; /// /// The pitch used on the voice. /// public float Pitch; /// /// The output volume of the voice. /// public float Volume; /// /// The previous output volume of the voice. /// public float PreviousVolume; /// /// Biquad filters to apply to the output of the voice. /// public Array2 BiquadFilters; /// /// Total count of of the voice. /// public uint WaveBuffersCount; /// /// Current playing of the voice. /// public uint WaveBuffersIndex; /// /// Change the behaviour of the voice. /// /// This was added on REV5. public DecodingBehaviour DecodingBehaviour; /// /// User state required by the data source. /// /// Only used for as the GC-ADPCM coefficients. public AddressInfo DataSourceStateAddressInfo; /// /// The wavebuffers of this voice. /// public Array4 WaveBuffers; /// /// The channel resource ids associated to the voice. /// public Array6 ChannelResourceIds; /// /// The target splitter id of the voice. /// public uint SplitterId; /// /// Change the Sample Rate Conversion (SRC) quality of the voice. /// /// This was added on REV8. public SampleRateConversionQuality SrcQuality; /// /// If set to true, the voice was dropped. /// [MarshalAs(UnmanagedType.I1)] public bool VoiceDropFlag; /// /// Set to true if the data source state work buffer wasn't mapped. /// [MarshalAs(UnmanagedType.I1)] public bool DataSourceStateUnmapped; /// /// Set to true if any of the work buffer wasn't mapped. /// [MarshalAs(UnmanagedType.I1)] public bool BufferInfoUnmapped; /// /// The biquad filter initialization state storage. /// private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization; /// /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. /// /// This was added on REV5. public byte FlushWaveBufferCount; [StructLayout(LayoutKind.Sequential, Size = Constants.VoiceBiquadFilterCount)] private struct BiquadFilterNeedInitializationArrayStruct { } /// /// The biquad filter initialization state array. /// public Span BiquadFilterNeedInitialization => SpanHelpers.AsSpan(ref _biquadFilterNeedInitialization); private static List _waveBufferUpdaterErrorInfosList; /// /// Initialize the . /// public void Initialize() { IsNew = false; VoiceDropFlag = false; DataSourceStateUnmapped = false; BufferInfoUnmapped = false; FlushWaveBufferCount = 0; PlayState = PlayState.Stopped; Priority = Constants.VoiceLowestPriority; Id = 0; NodeId = 0; SampleRate = 0; SampleFormat = SampleFormat.Invalid; ChannelsCount = 0; Pitch = 0.0f; Volume = 0.0f; PreviousVolume = 0.0f; BiquadFilters.AsSpan().Clear(); WaveBuffersCount = 0; WaveBuffersIndex = 0; MixId = Constants.UnusedMixId; SplitterId = Constants.UnusedSplitterId; DataSourceStateAddressInfo.Setup(0, 0); InitializeWaveBuffers(); _waveBufferUpdaterErrorInfosList ??= []; } /// /// Initialize the in this . /// private void InitializeWaveBuffers() { Span waveBuffersSpan = WaveBuffers.AsSpan(); for (int i = 0; i < waveBuffersSpan.Length; i++) { waveBuffersSpan[i].StartSampleOffset = 0; waveBuffersSpan[i].EndSampleOffset = 0; waveBuffersSpan[i].ShouldLoop = false; waveBuffersSpan[i].IsEndOfStream = false; waveBuffersSpan[i].BufferAddressInfo.Setup(0, 0); waveBuffersSpan[i].ContextAddressInfo.Setup(0, 0); waveBuffersSpan[i].IsSendToAudioProcessor = true; } } /// /// Check if the voice needs to be skipped. /// /// Returns true if the voice needs to be skipped. public readonly bool ShouldSkip() { return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag; } /// /// Return true if the mix has any destinations. /// /// True if the mix has any destinations. public readonly bool HasAnyDestination() { return MixId != Constants.UnusedMixId || SplitterId != Constants.UnusedSplitterId; } /// /// Indicate if the server voice information needs to be updated. /// /// The user parameter. /// Return true, if the server voice information needs to be updated. private readonly bool ShouldUpdateParameters2(in VoiceInParameter2 parameter) { if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) { return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize; } return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress || DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize || DataSourceStateUnmapped; } /// /// Indicate if the server voice information needs to be updated. /// /// The user parameter. /// Return true, if the server voice information needs to be updated. private readonly bool ShouldUpdateParameters1(in VoiceInParameter1 parameter) { if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) { return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize; } return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress || DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize || DataSourceStateUnmapped; } /// /// Update the internal state from a user parameter. /// /// The possible that was generated. /// The user parameter. /// The mapper to use. /// The behaviour context. public void UpdateParameters2(out ErrorInfo outErrorInfo, in VoiceInParameter2 parameter, PoolMapper poolMapper, ref BehaviourInfo behaviourInfo) { 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.AsSpan().CopyTo(BiquadFilters.AsSpan()); WaveBuffersCount = parameter.WaveBuffersCount; WaveBuffersIndex = parameter.WaveBuffersIndex; if (behaviourInfo.IsFlushVoiceWaveBuffersSupported()) { FlushWaveBufferCount += parameter.FlushWaveBufferCount; } MixId = parameter.MixId; if (behaviourInfo.IsSplitterSupported()) { SplitterId = parameter.SplitterId; } else { SplitterId = Constants.UnusedSplitterId; } parameter.ChannelResourceIds.AsSpan().CopyTo(ChannelResourceIds.AsSpan()); DecodingBehaviour behaviour = DecodingBehaviour.Default; if (behaviourInfo.IsDecodingBehaviourFlagSupported()) { behaviour = parameter.DecodingBehaviourFlags; } DecodingBehaviour = behaviour; if (parameter.ResetVoiceDropFlag) { VoiceDropFlag = false; } if (ShouldUpdateParameters2(in parameter)) { DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); } else { outErrorInfo = new ErrorInfo(); } } /// /// Update the internal state from a user parameter. /// /// The possible that was generated. /// The user paramter2. /// The mapper to use. /// The behaviour context. public void UpdateParameters1(out ErrorInfo outErrorInfo, in VoiceInParameter1 parameter, PoolMapper poolMapper, ref BehaviourInfo behaviourInfo) { 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; BiquadFilters[0] = BiquadFilterHelper.ToBiquadFilterParameter2(parameter.BiquadFilters[0]); BiquadFilters[1] = BiquadFilterHelper.ToBiquadFilterParameter2(parameter.BiquadFilters[1]); WaveBuffersCount = parameter.WaveBuffersCount; WaveBuffersIndex = parameter.WaveBuffersIndex; if (behaviourInfo.IsFlushVoiceWaveBuffersSupported()) { FlushWaveBufferCount += parameter.FlushWaveBufferCount; } MixId = parameter.MixId; if (behaviourInfo.IsSplitterSupported()) { SplitterId = parameter.SplitterId; } else { SplitterId = Constants.UnusedSplitterId; } parameter.ChannelResourceIds.AsSpan().CopyTo(ChannelResourceIds.AsSpan()); DecodingBehaviour behaviour = DecodingBehaviour.Default; if (behaviourInfo.IsDecodingBehaviourFlagSupported()) { behaviour = parameter.DecodingBehaviourFlags; } DecodingBehaviour = behaviour; if (parameter.ResetVoiceDropFlag) { VoiceDropFlag = false; } if (ShouldUpdateParameters1(in parameter)) { DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); } else { outErrorInfo = new ErrorInfo(); } } /// /// Update the internal play state from user play state. /// /// The target user play state. public void UpdatePlayState(Common.PlayState userPlayState) { PlayState oldServerPlayState = PlayState; PreviousPlayState = oldServerPlayState; PlayState newServerPlayState; switch (userPlayState) { case Common.PlayState.Start: newServerPlayState = PlayState.Started; break; case Common.PlayState.Stop: if (oldServerPlayState == PlayState.Stopped) { return; } newServerPlayState = PlayState.Stopping; break; case Common.PlayState.Pause: newServerPlayState = PlayState.Paused; break; default: throw new NotImplementedException($"Unhandled PlayState.{userPlayState}"); } PlayState = newServerPlayState; } /// /// Write the status of the voice to the given user output. /// /// The given user output. /// The user parameter. /// The voice states associated to the . public void WriteOutStatus2(ref VoiceOutStatus outStatus, in VoiceInParameter2 parameter, ReadOnlySpan> voiceStates) { #if DEBUG // Sanity check in debug mode of the internal state if (!parameter.IsNew && !IsNew) { for (int i = 1; i < ChannelsCount; i++) { ref VoiceState stateA = ref voiceStates[i - 1].Span[0]; ref VoiceState stateB = ref voiceStates[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 VoiceState state = ref voiceStates[0].Span[0]; outStatus.VoiceDropFlag = VoiceDropFlag; outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed; outStatus.PlayedSampleCount = state.PlayedSampleCount; } } /// /// Write the status of the voice to the given user output. /// /// The given user output. /// The user parameter. /// The voice states associated to the . public void WriteOutStatus1(ref VoiceOutStatus outStatus, in VoiceInParameter1 parameter, ReadOnlySpan> voiceStates) { #if DEBUG // Sanity check in debug mode of the internal state if (!parameter.IsNew && !IsNew) { for (int i = 1; i < ChannelsCount; i++) { ref VoiceState stateA = ref voiceStates[i - 1].Span[0]; ref VoiceState stateB = ref voiceStates[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 VoiceState state = ref voiceStates[0].Span[0]; outStatus.VoiceDropFlag = VoiceDropFlag; outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed; outStatus.PlayedSampleCount = state.PlayedSampleCount; } } /// /// Update the internal state of all the of the . /// /// An array of used to report errors when mapping any of the . /// The user parameter. /// The voice states associated to the . /// The mapper to use. /// The behaviour context. public void UpdateWaveBuffers2( out ErrorInfo[] errorInfos, in VoiceInParameter2 parameter, ReadOnlySpan> voiceStates, PoolMapper mapper, ref BehaviourInfo behaviourInfo) { if (parameter.IsNew) { InitializeWaveBuffers(); for (int i = 0; i < parameter.ChannelCount; i++) { voiceStates[i].Span[0].IsWaveBufferValid.Clear(); } } ref VoiceState voiceState = ref voiceStates[0].Span[0]; Span waveBuffersSpan = WaveBuffers.AsSpan(); Span pWaveBuffersSpan = parameter.WaveBuffers.AsSpan(); _waveBufferUpdaterErrorInfosList.Clear(); for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) { UpdateWaveBuffer(_waveBufferUpdaterErrorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo); } errorInfos = _waveBufferUpdaterErrorInfosList.ToArray(); } /// /// Update the internal state of all the of the . /// /// An array of used to report errors when mapping any of the . /// The user parameter. /// The voice states associated to the . /// The mapper to use. /// The behaviour context. public void UpdateWaveBuffers1( out ErrorInfo[] errorInfos, in VoiceInParameter1 parameter, ReadOnlySpan> voiceStates, PoolMapper mapper, ref BehaviourInfo behaviourInfo) { if (parameter.IsNew) { InitializeWaveBuffers(); for (int i = 0; i < parameter.ChannelCount; i++) { voiceStates[i].Span[0].IsWaveBufferValid.Clear(); } } ref VoiceState voiceState = ref voiceStates[0].Span[0]; Span waveBuffersSpan = WaveBuffers.AsSpan(); Span pWaveBuffersSpan = parameter.WaveBuffers.AsSpan(); _waveBufferUpdaterErrorInfosList.Clear(); for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) { UpdateWaveBuffer(_waveBufferUpdaterErrorInfosList, ref waveBuffersSpan[i], ref pWaveBuffersSpan[i], parameter.SampleFormat, voiceState.IsWaveBufferValid[i], mapper, ref behaviourInfo); } errorInfos = _waveBufferUpdaterErrorInfosList.ToArray(); } /// /// Update the internal state of one of the of the . /// /// A used to report errors when mapping the . /// The to update. /// The from the user input. /// The from the user input. /// If set to true, the server side wavebuffer is considered valid. /// The mapper to use. /// The behaviour context. private void UpdateWaveBuffer( List errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, PoolMapper mapper, ref BehaviourInfo behaviourInfo) { 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); if (bufferInfoError.ErrorCode != ResultCode.Success) { errorInfos.Add(bufferInfoError); } if (sampleFormat == SampleFormat.Adpcm && behaviourInfo.IsAdpcmLoopContextBugFixed() && inputWaveBuffer.ContextAddress != 0) { bool adpcmLoopContextMapped = mapper.TryAttachBuffer(out ErrorInfo adpcmLoopContextInfoError, ref waveBuffer.ContextAddressInfo, inputWaveBuffer.ContextAddress, inputWaveBuffer.ContextSize); if (adpcmLoopContextInfoError.ErrorCode != ResultCode.Success) { errorInfos.Add(adpcmLoopContextInfoError); } if (!adpcmLoopContextMapped || BufferInfoUnmapped) { BufferInfoUnmapped = true; } else { BufferInfoUnmapped = false; } } else { waveBuffer.ContextAddressInfo.Setup(0, 0); } } else { errorInfos.Add(new ErrorInfo { ErrorCode = ResultCode.InvalidAddressInfo, ExtraErrorInfo = inputWaveBuffer.Address }); } } } /// /// Reset the resources associated to this . /// /// The voice context. private void ResetResources(VoiceContext context) { Span channelResourceIdsSpan = ChannelResourceIds.AsSpan(); for (int i = 0; i < ChannelsCount; i++) { int channelResourceId = channelResourceIdsSpan[i]; ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId); Debug.Assert(voiceChannelResource.IsUsed); Memory dspSharedState = context.GetUpdateStateForDsp(channelResourceId); MemoryMarshal.Cast(dspSharedState.Span).Clear(); voiceChannelResource.UpdateState(); } } /// /// Flush a certain amount of . /// /// The amount of wavebuffer to flush. /// The voice states associated to the . /// The channel count from user input. private void FlushWaveBuffers(uint waveBufferCount, Memory[] voiceStates, uint channelCount) { uint waveBufferIndex = WaveBuffersIndex; Span waveBuffersSpan = WaveBuffers.AsSpan(); for (int i = 0; i < waveBufferCount; i++) { waveBuffersSpan[(int)waveBufferIndex].IsSendToAudioProcessor = true; for (int j = 0; j < channelCount; j++) { ref VoiceState voiceState = ref voiceStates[j].Span[0]; if (!waveBuffersSpan[(int)waveBufferIndex].IsSendToAudioProcessor || voiceState.IsWaveBufferValid[(int)waveBufferIndex]) { voiceState.WaveBufferIndex = (voiceState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount; voiceState.WaveBufferConsumed++; voiceState.IsWaveBufferValid[(int)waveBufferIndex] = false; } } waveBuffersSpan[(int)waveBufferIndex].IsSendToAudioProcessor = true; waveBufferIndex = (waveBufferIndex + 1) % Constants.VoiceWaveBufferCount; } } /// /// Update the internal parameters for command generation. /// /// The voice states associated to the . /// Return true if this voice should be played. public bool UpdateParametersForCommandGeneration(Memory[] voiceStates) { if (FlushWaveBufferCount != 0) { FlushWaveBuffers(FlushWaveBufferCount, voiceStates, ChannelsCount); FlushWaveBufferCount = 0; } Span waveBuffersSpan; switch (PlayState) { case PlayState.Started: waveBuffersSpan = WaveBuffers.AsSpan(); for (int i = 0; i < waveBuffersSpan.Length; i++) { ref WaveBuffer waveBuffer = ref waveBuffersSpan[i]; if (!waveBuffer.IsSendToAudioProcessor) { for (int y = 0; y < ChannelsCount; y++) { Debug.Assert(!voiceStates[y].Span[0].IsWaveBufferValid[i]); voiceStates[y].Span[0].IsWaveBufferValid[i] = true; } waveBuffer.IsSendToAudioProcessor = true; } } WasPlaying = false; ref VoiceState primaryVoiceState = ref voiceStates[0].Span[0]; for (int i = 0; i < primaryVoiceState.IsWaveBufferValid.Length; i++) { if (primaryVoiceState.IsWaveBufferValid[i]) { return true; } } return false; case PlayState.Stopping: waveBuffersSpan = WaveBuffers.AsSpan(); for (int i = 0; i < waveBuffersSpan.Length; i++) { ref WaveBuffer waveBuffer = ref waveBuffersSpan[i]; waveBuffer.IsSendToAudioProcessor = true; for (int j = 0; j < ChannelsCount; j++) { ref VoiceState voiceState = ref voiceStates[j].Span[0]; if (voiceState.IsWaveBufferValid[i]) { voiceState.WaveBufferIndex = (voiceState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount; voiceState.WaveBufferConsumed++; } voiceState.IsWaveBufferValid[i] = false; } } for (int i = 0; i < ChannelsCount; i++) { ref VoiceState voiceState = ref voiceStates[i].Span[0]; voiceState.Offset = 0; voiceState.PlayedSampleCount = 0; voiceState.Pitch.AsSpan().Clear(); voiceState.Fraction = 0; voiceState.LoopContext = new AdpcmLoopContext(); } PlayState = PlayState.Stopped; WasPlaying = PreviousPlayState == PlayState.Started; return WasPlaying; case PlayState.Stopped: case PlayState.Paused: foreach (ref WaveBuffer wavebuffer in WaveBuffers.AsSpan()) { wavebuffer.BufferAddressInfo.GetReference(true); wavebuffer.ContextAddressInfo.GetReference(true); } if (SampleFormat == SampleFormat.Adpcm) { if (DataSourceStateAddressInfo.CpuAddress != 0) { DataSourceStateAddressInfo.GetReference(true); } } WasPlaying = PreviousPlayState == PlayState.Started; return WasPlaying; default: throw new NotImplementedException($"{PlayState}"); } } /// /// Update the internal state for command generation. /// /// The voice context. /// Return true if this voice should be played. public bool UpdateForCommandGeneration(VoiceContext context) { if (IsNew) { ResetResources(context); PreviousVolume = Volume; IsNew = false; } Memory[] voiceStates = voiceStatesPool.Allocate(); Span channelResourceIdsSpan = ChannelResourceIds.AsSpan(); for (int i = 0; i < ChannelsCount; i++) { voiceStates[i] = context.GetUpdateStateForDsp(channelResourceIdsSpan[i]); } bool result = UpdateParametersForCommandGeneration(voiceStates); voiceStatesPool.Release(voiceStates); //might contain garbage data, but said data will never be accessed return result; } } }