Ambisonic.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. using UnityEngine;
  2. using System;
  3. using System.Runtime.InteropServices;
  4. #if UNITY_IOS || UNITY_TVOS || ENABLE_IL2CPP
  5. using AOT;
  6. #endif
  7. //-----------------------------------------------------------------------------
  8. // Copyright 2012-2022 RenderHeads Ltd. All rights reserved.
  9. //-----------------------------------------------------------------------------
  10. namespace RenderHeads.Media.AVProMovieCapture
  11. {
  12. public enum AmbisonicOrder : int
  13. {
  14. //Zero = 0,
  15. First = 1,
  16. Second = 2,
  17. Third = 3,
  18. }
  19. public enum AmbisonicFormat
  20. {
  21. FuMa, // FuMa channel ordering and normalisation
  22. ACN_SN3D, // ACN channel ordering with SN3D normalisation
  23. }
  24. public enum AmbisonicChannelOrder : int
  25. {
  26. FuMa,
  27. ACN,
  28. }
  29. public enum AmbisonicNormalisation : int
  30. {
  31. FuMa,
  32. SN3D,
  33. }
  34. public partial class NativePlugin
  35. {
  36. //////////////////////////////////////////////////////////////////////////
  37. // Ambisonic
  38. [DllImport(PluginName, EntryPoint="AVPMC_AddAmbisonicSourceInstance")]
  39. public static extern IntPtr AddAmbisonicSourceInstance(int maxCoefficients);
  40. [DllImport(PluginName, EntryPoint="AVPMC_RemoveAmbisonicSourceInstance")]
  41. public static extern void RemoveAmbisonicSourceInstance(IntPtr instance);
  42. [DllImport(PluginName, EntryPoint = "AVPMC_UpdateAmbisonicWeights")]
  43. public static extern void UpdateAmbisonicWeights(IntPtr instance, float azimuth, float elevation, AmbisonicOrder order, AmbisonicChannelOrder channelOrder, float[] normalisationWeights);
  44. [DllImport(PluginName, EntryPoint = "AVPMC_EncodeMonoToAmbisonic")]
  45. public static extern void EncodeMonoToAmbisonic(IntPtr instance, float[] inSamples, int inSamplesOffset, int inFrameCount, int inChannelCount, float[] outSamples, int outSamplesOffset, int outSamplesLength, AmbisonicOrder order);
  46. }
  47. /// <summary>
  48. /// Internal helper class for encoding ambisonic audio
  49. /// </summary>
  50. public static class Ambisonic
  51. {
  52. public const int MaxCoeffs = 16;
  53. static float[] _weightsFuMa = null;
  54. static float[] _weightsSN3D = null;
  55. public static float[] GetNormalisationWeights(AmbisonicNormalisation normalisation)
  56. {
  57. return (normalisation == AmbisonicNormalisation.FuMa) ? _weightsFuMa : _weightsSN3D;
  58. }
  59. public static int GetCoeffCount(AmbisonicOrder order)
  60. {
  61. if (order == AmbisonicOrder.First) { return 4; }
  62. else if (order == AmbisonicOrder.Second) { return 9; }
  63. else if (order == AmbisonicOrder.Third) { return 16; }
  64. return 0;
  65. }
  66. public static AmbisonicChannelOrder GetChannelOrder(AmbisonicFormat format)
  67. {
  68. return (format == AmbisonicFormat.FuMa) ? AmbisonicChannelOrder.FuMa : AmbisonicChannelOrder.ACN;
  69. }
  70. public static AmbisonicNormalisation GetNormalisation(AmbisonicFormat format)
  71. {
  72. return (format == AmbisonicFormat.FuMa) ? AmbisonicNormalisation.FuMa : AmbisonicNormalisation.SN3D;
  73. }
  74. static Ambisonic()
  75. {
  76. _weightsFuMa = BuildWeightsFuMa();
  77. _weightsSN3D = BuildWeightsSN3D();
  78. }
  79. static float[] BuildWeightsFuMa()
  80. {
  81. float[] w = new float[MaxCoeffs];
  82. w[0] = 1f / Mathf.Sqrt(2f);
  83. w[1] = 1f;
  84. w[2] = 1f;
  85. w[3] = 1f;
  86. w[4] = 1f;
  87. w[5] = 2f / Mathf.Sqrt(3f);
  88. w[6] = 2f / Mathf.Sqrt(3f);
  89. w[7] = 2f / Mathf.Sqrt(3f);
  90. w[8] = 2f / Mathf.Sqrt(3f);
  91. w[9] = 1f;
  92. w[10] = Mathf.Sqrt(45f / 32f);
  93. w[11] = Mathf.Sqrt(45f / 32f);
  94. w[12] = 3f / Mathf.Sqrt(5f);
  95. w[13] = 3f / Mathf.Sqrt(5f);
  96. w[14] = Mathf.Sqrt(8f / 5f);
  97. w[15] = Mathf.Sqrt(8f / 5f);
  98. return w;
  99. }
  100. // Returns N which is the same as the order
  101. static int GetN(int acn)
  102. {
  103. return Mathf.FloorToInt(Mathf.Sqrt(acn));
  104. }
  105. // Returns M which is the signed delta offset from the middle of the pyramid
  106. static int GetM(int acn)
  107. {
  108. int n = GetN(acn);
  109. return acn - (n * n) - n;
  110. }
  111. static int Factorial(int x)
  112. {
  113. int result = 1;
  114. for (int i = 2; i <= x; i++)
  115. {
  116. result *= i;
  117. }
  118. return result;
  119. }
  120. static float GetNormalisationSN3D(int acn)
  121. {
  122. int n = GetN(acn);
  123. int m = GetM(acn);
  124. return GetNormalisationSN3D(n, m);
  125. }
  126. static float GetNormalisationSN3D(int n, int m)
  127. {
  128. float dm = (m == 0) ? 1f : 0f;
  129. float l1 = (2f - dm);
  130. float a = Factorial(n - Mathf.Abs(m));
  131. float b = Factorial(n + Mathf.Abs(m));
  132. float l2 = a / b;
  133. return Mathf.Sqrt(l1 * l2);
  134. }
  135. static float GetNormalisationN3D(int n, int m)
  136. {
  137. return GetNormalisationSN3D(n, m) * Mathf.Sqrt(2f * n + 1f);
  138. }
  139. static float[] BuildWeightsSN3D()
  140. {
  141. float[] w = new float[MaxCoeffs];
  142. for (int acn = 0; acn < w.Length; acn++)
  143. {
  144. w[acn] = GetNormalisationSN3D(acn);
  145. }
  146. return w;
  147. }
  148. /// <summary>
  149. /// The coordinate system used in Ambisonics follows the right hand rule convention with
  150. /// positive X pointing forwards,
  151. /// positive Y pointing to the left and
  152. /// positive Z pointing upwards
  153. /// Horizontal angles run anticlockwise from due front and
  154. /// vertical angles are positive above the horizontal, negative below.
  155. /// </summary>
  156. internal struct PolarCoord
  157. {
  158. /// Azimuth (horizontal) angle in radians, 0..2PI
  159. public float azimuth;
  160. /// Elevation (vertical) angle in radians, -PI..PI
  161. public float elevation;
  162. //public float distance;
  163. public void FromCart(Vector3 position)
  164. {
  165. // Convert from Unity's left-hand system to Ambisonics right-hand system
  166. float x = position.z;
  167. float y = -position.x;
  168. float z = position.y;
  169. // The azimuth angle is zero straight ahead and increases counter-clockwise.
  170. azimuth = Mathf.Rad2Deg * Mathf.Atan2(y, x);
  171. // Clamp
  172. if (azimuth < 0f)
  173. {
  174. azimuth += 360f;
  175. }
  176. // The elevation angle is zero on the horizontal plane and positive in the upper hemisphere.
  177. elevation = Mathf.Rad2Deg * Mathf.Atan2(z, Mathf.Sqrt(x * x + y * y));
  178. elevation = Mathf.Clamp(elevation, -90f, 90f);
  179. // NOTE: Distance is not currently used, but there may be scope in the future
  180. //distance = Mathf.Sqrt( x * x + y * y + z * z );
  181. // Back to radians
  182. azimuth = Mathf.Deg2Rad * azimuth;
  183. elevation = Mathf.Deg2Rad * elevation;
  184. }
  185. };
  186. }
  187. }