361 lines
12 KiB
C#
361 lines
12 KiB
C#
|
//
|
||
|
// Copyright (c) 2019-2020 Ryujinx
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU Lesser General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Lesser General Public License
|
||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
//
|
||
|
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
}
|