MixState.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. using Ryujinx.Audio.Renderer.Common;
  2. using Ryujinx.Audio.Renderer.Parameter;
  3. using Ryujinx.Audio.Renderer.Server.Effect;
  4. using Ryujinx.Audio.Renderer.Server.Splitter;
  5. using Ryujinx.Common.Utilities;
  6. using System;
  7. using System.Diagnostics;
  8. using System.Runtime.CompilerServices;
  9. using System.Runtime.InteropServices;
  10. using static Ryujinx.Audio.Constants;
  11. namespace Ryujinx.Audio.Renderer.Server.Mix
  12. {
  13. /// <summary>
  14. /// Server state for a mix.
  15. /// </summary>
  16. [StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)]
  17. public struct MixState
  18. {
  19. public const uint InvalidDistanceFromFinalMix = 0x80000000;
  20. public const int Alignment = 0x10;
  21. /// <summary>
  22. /// Base volume of the mix.
  23. /// </summary>
  24. public float Volume;
  25. /// <summary>
  26. /// Target sample rate of the mix.
  27. /// </summary>
  28. public uint SampleRate;
  29. /// <summary>
  30. /// Target buffer count.
  31. /// </summary>
  32. public uint BufferCount;
  33. /// <summary>
  34. /// Set to true if in use.
  35. /// </summary>
  36. [MarshalAs(UnmanagedType.I1)]
  37. public bool IsUsed;
  38. /// <summary>
  39. /// The id of the mix.
  40. /// </summary>
  41. public int MixId;
  42. /// <summary>
  43. /// The mix node id.
  44. /// </summary>
  45. public int NodeId;
  46. /// <summary>
  47. /// the buffer offset to use for command generation.
  48. /// </summary>
  49. public uint BufferOffset;
  50. /// <summary>
  51. /// The distance of the mix from the final mix.
  52. /// </summary>
  53. public uint DistanceFromFinalMix;
  54. /// <summary>
  55. /// The effect processing order storage.
  56. /// </summary>
  57. private IntPtr _effectProcessingOrderArrayPointer;
  58. /// <summary>
  59. /// The max element count that can be found in the effect processing order storage.
  60. /// </summary>
  61. public uint EffectProcessingOrderArrayMaxCount;
  62. /// <summary>
  63. /// The mix to output the result of this mix.
  64. /// </summary>
  65. public int DestinationMixId;
  66. /// <summary>
  67. /// Mix buffer volumes storage.
  68. /// </summary>
  69. private MixVolumeArray _mixVolumeArray;
  70. /// <summary>
  71. /// The splitter to output the result of this mix.
  72. /// </summary>
  73. public uint DestinationSplitterId;
  74. /// <summary>
  75. /// If set to true, the long size pre-delay is supported on the reverb command.
  76. /// </summary>
  77. [MarshalAs(UnmanagedType.I1)]
  78. public bool IsLongSizePreDelaySupported;
  79. [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
  80. private struct MixVolumeArray
  81. {
  82. private const int Size = 4 * MixBufferCountMax * MixBufferCountMax;
  83. }
  84. /// <summary>
  85. /// Mix buffer volumes.
  86. /// </summary>
  87. /// <remarks>Used when no splitter id is specified.</remarks>
  88. public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixVolumeArray, float>(ref _mixVolumeArray);
  89. /// <summary>
  90. /// Get the volume for a given connection destination.
  91. /// </summary>
  92. /// <param name="sourceIndex">The source node index.</param>
  93. /// <param name="destinationIndex">The destination node index</param>
  94. /// <returns>The volume for the given connection destination.</returns>
  95. public float GetMixBufferVolume(int sourceIndex, int destinationIndex)
  96. {
  97. return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex];
  98. }
  99. /// <summary>
  100. /// The array used to order effects associated to this mix.
  101. /// </summary>
  102. public Span<int> EffectProcessingOrderArray
  103. {
  104. get
  105. {
  106. if (_effectProcessingOrderArrayPointer == IntPtr.Zero)
  107. {
  108. return Span<int>.Empty;
  109. }
  110. unsafe
  111. {
  112. return new Span<int>((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount);
  113. }
  114. }
  115. }
  116. /// <summary>
  117. /// Create a new <see cref="MixState"/>
  118. /// </summary>
  119. /// <param name="effectProcessingOrderArray"></param>
  120. /// <param name="behaviourContext"></param>
  121. public MixState(Memory<int> effectProcessingOrderArray, ref BehaviourContext behaviourContext) : this()
  122. {
  123. MixId = UnusedMixId;
  124. DistanceFromFinalMix = InvalidDistanceFromFinalMix;
  125. DestinationMixId = UnusedMixId;
  126. DestinationSplitterId = UnusedSplitterId;
  127. unsafe
  128. {
  129. // SAFETY: safe as effectProcessingOrderArray comes from the work buffer memory that is pinned.
  130. _effectProcessingOrderArrayPointer = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(effectProcessingOrderArray.Span));
  131. }
  132. EffectProcessingOrderArrayMaxCount = (uint)effectProcessingOrderArray.Length;
  133. IsLongSizePreDelaySupported = behaviourContext.IsLongSizePreDelaySupported();
  134. ClearEffectProcessingOrder();
  135. }
  136. /// <summary>
  137. /// Clear the <see cref="DistanceFromFinalMix"/> value to its default state.
  138. /// </summary>
  139. public void ClearDistanceFromFinalMix()
  140. {
  141. DistanceFromFinalMix = InvalidDistanceFromFinalMix;
  142. }
  143. /// <summary>
  144. /// Clear the <see cref="EffectProcessingOrderArray"/> to its default state.
  145. /// </summary>
  146. public void ClearEffectProcessingOrder()
  147. {
  148. EffectProcessingOrderArray.Fill(-1);
  149. }
  150. /// <summary>
  151. /// Return true if the mix has any destinations.
  152. /// </summary>
  153. /// <returns>True if the mix has any destinations.</returns>
  154. public bool HasAnyDestination()
  155. {
  156. return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId;
  157. }
  158. /// <summary>
  159. /// Update the mix connection on the adjacency matrix.
  160. /// </summary>
  161. /// <param name="edgeMatrix">The adjacency matrix.</param>
  162. /// <param name="parameter">The input parameter of the mix.</param>
  163. /// <param name="splitterContext">The splitter context.</param>
  164. /// <returns>Return true, new connections were done on the adjacency matrix.</returns>
  165. private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext)
  166. {
  167. bool hasNewConnections;
  168. if (DestinationSplitterId == UnusedSplitterId)
  169. {
  170. hasNewConnections = false;
  171. }
  172. else
  173. {
  174. ref SplitterState splitter = ref splitterContext.GetState((int)DestinationSplitterId);
  175. hasNewConnections = splitter.HasNewConnection;
  176. }
  177. if (DestinationMixId == parameter.DestinationMixId && DestinationSplitterId == parameter.DestinationSplitterId && !hasNewConnections)
  178. {
  179. return false;
  180. }
  181. edgeMatrix.RemoveEdges(MixId);
  182. if (parameter.DestinationMixId == UnusedMixId)
  183. {
  184. if (parameter.DestinationSplitterId != UnusedSplitterId)
  185. {
  186. ref SplitterState splitter = ref splitterContext.GetState((int)parameter.DestinationSplitterId);
  187. for (int i = 0; i < splitter.DestinationCount; i++)
  188. {
  189. Span<SplitterDestination> destination = splitter.GetData(i);
  190. if (!destination.IsEmpty)
  191. {
  192. int destinationMixId = destination[0].DestinationId;
  193. if (destinationMixId != UnusedMixId)
  194. {
  195. edgeMatrix.Connect(MixId, destinationMixId);
  196. }
  197. }
  198. }
  199. }
  200. }
  201. else
  202. {
  203. edgeMatrix.Connect(MixId, parameter.DestinationMixId);
  204. }
  205. DestinationMixId = parameter.DestinationMixId;
  206. DestinationSplitterId = parameter.DestinationSplitterId;
  207. return true;
  208. }
  209. /// <summary>
  210. /// Update the mix from user information.
  211. /// </summary>
  212. /// <param name="edgeMatrix">The adjacency matrix.</param>
  213. /// <param name="parameter">The input parameter of the mix.</param>
  214. /// <param name="effectContext">The effect context.</param>
  215. /// <param name="splitterContext">The splitter context.</param>
  216. /// <param name="behaviourContext">The behaviour context.</param>
  217. /// <returns>Return true if the mix was changed.</returns>
  218. public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext)
  219. {
  220. bool isDirty;
  221. Volume = parameter.Volume;
  222. SampleRate = parameter.SampleRate;
  223. BufferCount = parameter.BufferCount;
  224. IsUsed = parameter.IsUsed;
  225. MixId = parameter.MixId;
  226. NodeId = parameter.NodeId;
  227. parameter.MixBufferVolume.CopyTo(MixBufferVolume);
  228. if (behaviourContext.IsSplitterSupported())
  229. {
  230. isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext);
  231. }
  232. else
  233. {
  234. isDirty = DestinationMixId != parameter.DestinationMixId;
  235. if (DestinationMixId != parameter.DestinationMixId)
  236. {
  237. DestinationMixId = parameter.DestinationMixId;
  238. }
  239. DestinationSplitterId = UnusedSplitterId;
  240. }
  241. ClearEffectProcessingOrder();
  242. for (int i = 0; i < effectContext.GetCount(); i++)
  243. {
  244. ref BaseEffect effect = ref effectContext.GetEffect(i);
  245. if (effect.MixId == MixId)
  246. {
  247. Debug.Assert(effect.ProcessingOrder <= EffectProcessingOrderArrayMaxCount);
  248. if (effect.ProcessingOrder > EffectProcessingOrderArrayMaxCount)
  249. {
  250. return isDirty;
  251. }
  252. EffectProcessingOrderArray[(int)effect.ProcessingOrder] = i;
  253. }
  254. }
  255. return isDirty;
  256. }
  257. }
  258. }