2020-08-17 18:49:37 -07:00
//
2020-12-31 15:10:44 -08:00
// Copyright (c) 2019-2021 Ryujinx
2020-08-17 18:49:37 -07:00
//
// 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/>.
//
2021-02-25 16:11:56 -08:00
using Ryujinx.Audio.Common ;
2020-08-17 18:49:37 -07:00
using Ryujinx.Audio.Renderer.Common ;
using Ryujinx.Audio.Renderer.Dsp ;
using Ryujinx.Common.Memory ;
using System ;
using System.Runtime.CompilerServices ;
using System.Runtime.InteropServices ;
namespace Ryujinx.Audio.Renderer.Parameter
{
/// <summary>
/// Input information for a voice.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)]
public struct VoiceInParameter
{
/// <summary>
/// Id of the voice.
/// </summary>
public int Id ;
/// <summary>
/// Node id of the voice.
/// </summary>
public int NodeId ;
/// <summary>
/// Set to true if the voice is new.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsNew ;
/// <summary>
/// Set to true if the voice is used.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool InUse ;
/// <summary>
/// The voice <see cref="PlayState"/> wanted by the user.
/// </summary>
public PlayState PlayState ;
/// <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 priority of the voice.
/// </summary>
public uint Priority ;
/// <summary>
/// Target sorting position of the voice. (Used to sort voices with the same <see cref="Priority"/>)
/// </summary>
public uint SortingOrder ;
/// <summary>
/// The total channel count used.
/// </summary>
public uint ChannelCount ;
/// <summary>
/// The pitch used on the voice.
/// </summary>
public float Pitch ;
/// <summary>
/// The output volume of the voice.
/// </summary>
public float Volume ;
/// <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>
/// Reserved/unused.
/// </summary>
private uint _reserved1 ;
/// <summary>
/// User state address required by the data source.
/// </summary>
/// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the address of the GC-ADPCM coefficients.</remarks>
public ulong DataSourceStateAddress ;
/// <summary>
/// User state size required by the data source.
/// </summary>
/// <remarks>Only used for <see cref="SampleFormat.Adpcm"/> as the size of the GC-ADPCM coefficients.</remarks>
public ulong DataSourceStateSize ;
/// <summary>
/// The target mix id of the voice.
/// </summary>
public int MixId ;
/// <summary>
/// The target splitter id of the voice.
/// </summary>
public uint SplitterId ;
/// <summary>
/// The wavebuffer parameters of this voice.
/// </summary>
public Array4 < WaveBufferInternal > WaveBuffers ;
/// <summary>
/// The channel resource ids associated to the voice.
/// </summary>
public Array6 < int > ChannelResourceIds ;
/// <summary>
/// Reset the voice drop flag during voice server update.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool ResetVoiceDropFlag ;
/// <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 ;
/// <summary>
/// Reserved/unused.
/// </summary>
private ushort _reserved2 ;
/// <summary>
/// Change the behaviour of the voice.
/// </summary>
/// <remarks>This was added on REV5.</remarks>
public DecodingBehaviour DecodingBehaviourFlags ;
/// <summary>
/// Change the Sample Rate Conversion (SRC) quality of the voice.
/// </summary>
/// <remarks>This was added on REV8.</remarks>
public SampleRateConversionQuality SrcQuality ;
/// <summary>
/// This was previously used for opus codec support on the Audio Renderer and was removed on REV3.
/// </summary>
public uint ExternalContext ;
/// <summary>
/// This was previously used for opus codec support on the Audio Renderer and was removed on REV3.
/// </summary>
public uint ExternalContextSize ;
/// <summary>
/// Reserved/unused.
/// </summary>
private unsafe fixed uint _reserved3 [ 2 ] ;
/// <summary>
/// Input information for a voice wavebuffer.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)]
public struct WaveBufferInternal
{
/// <summary>
/// Address of the wavebuffer data.
/// </summary>
public ulong Address ;
/// <summary>
/// Size of the wavebuffer data.
/// </summary>
public ulong Size ;
/// <summary>
/// Offset of the first sample to play.
/// </summary>
public uint StartSampleOffset ;
/// <summary>
/// Offset of the last sample to play.
/// </summary>
public uint EndSampleOffset ;
/// <summary>
/// If set to true, the wavebuffer will loop when reaching <see cref="EndSampleOffset"/>.
/// </summary>
/// <remarks>
/// Starting with REV8, you can specify how many times to loop the wavebuffer (<see cref="LoopCount"/>) and where it should start and end when looping (<see cref="LoopFirstSampleOffset"/> and <see cref="LoopLastSampleOffset"/>)
/// </remarks>
[MarshalAs(UnmanagedType.I1)]
public bool ShouldLoop ;
/// <summary>
/// Indicates that this is the last wavebuffer to play of the voice.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool IsEndOfStream ;
/// <summary>
/// Indicates if the server should update its internal state.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool SentToServer ;
/// <summary>
/// Reserved/unused.
/// </summary>
private byte _reserved ;
/// <summary>
/// If set to anything other than 0, specifies how many times to loop the wavebuffer.
/// </summary>
/// <remarks>This was added in REV8.</remarks>
public int LoopCount ;
/// <summary>
/// Address of the context used by the sample decoder.
/// </summary>
/// <remarks>This is only currently used by <see cref="SampleFormat.Adpcm"/>.</remarks>
public ulong ContextAddress ;
/// <summary>
/// Size of the context used by the sample decoder.
/// </summary>
/// <remarks>This is only currently used by <see cref="SampleFormat.Adpcm"/>.</remarks>
public ulong ContextSize ;
/// <summary>
/// If set to anything other than 0, specifies the offset of the first sample to play when looping.
/// </summary>
/// <remarks>This was added in REV8.</remarks>
public uint LoopFirstSampleOffset ;
/// <summary>
/// If set to anything other than 0, specifies the offset of the last sample to play when looping.
/// </summary>
/// <remarks>This was added in REV8.</remarks>
public uint LoopLastSampleOffset ;
/// <summary>
/// Check if the sample offsets are in a valid range for generic PCM.
/// </summary>
/// <typeparam name="T">The PCM sample type</typeparam>
/// <returns>Returns true if the sample offset are in range of the size.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsSampleOffsetInRangeForPcm < T > ( ) where T : unmanaged
{
uint dataTypeSize = ( uint ) Unsafe . SizeOf < T > ( ) ;
return StartSampleOffset * dataTypeSize < = Size & &
EndSampleOffset * dataTypeSize < = Size ;
}
/// <summary>
/// Check if the sample offsets are in a valid range for the given <see cref="SampleFormat"/>.
/// </summary>
/// <param name="format">The target <see cref="SampleFormat"/></param>
/// <returns>Returns true if the sample offset are in range of the size.</returns>
public bool IsSampleOffsetValid ( SampleFormat format )
{
bool result ;
switch ( format )
{
case SampleFormat . PcmInt16 :
result = IsSampleOffsetInRangeForPcm < ushort > ( ) ;
break ;
case SampleFormat . PcmFloat :
result = IsSampleOffsetInRangeForPcm < float > ( ) ;
break ;
case SampleFormat . Adpcm :
result = AdpcmHelper . GetAdpcmDataSize ( ( int ) StartSampleOffset ) < = Size & &
AdpcmHelper . GetAdpcmDataSize ( ( int ) EndSampleOffset ) < = Size ;
break ;
default :
throw new NotImplementedException ( $"{format} not implemented!" ) ;
}
return result ;
}
}
/// <summary>
/// Flag altering the behaviour of wavebuffer decoding.
/// </summary>
[Flags]
public enum DecodingBehaviour : ushort
{
/// <summary>
/// Default decoding behaviour.
/// </summary>
Default = 0 ,
/// <summary>
/// Reset the played samples accumulator when looping.
/// </summary>
PlayedSampleCountResetWhenLooping = 1 ,
/// <summary>
/// Skip pitch and Sample Rate Conversion (SRC).
/// </summary>
SkipPitchAndSampleRateConversion = 2
}
/// <summary>
/// Specify the quality to use during Sample Rate Conversion (SRC) and pitch handling.
/// </summary>
/// <remarks>This was added in REV8.</remarks>
public enum SampleRateConversionQuality : byte
{
/// <summary>
/// Resample interpolating 4 samples per output sample.
/// </summary>
Default ,
/// <summary>
/// Resample interpolating 8 samples per output sample.
/// </summary>
High ,
/// <summary>
/// Resample interpolating 1 samples per output sample.
/// </summary>
Low
}
}
}