AmbisonicSource.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. //-----------------------------------------------------------------------------
  4. // Copyright 2012-2022 RenderHeads Ltd. All rights reserved.
  5. //-----------------------------------------------------------------------------
  6. namespace RenderHeads.Media.AVProMovieCapture
  7. {
  8. /// <summary>
  9. /// Plays an AudioSource (which should only have a single mono channel) for ambisonic encoding
  10. /// </summary>
  11. [AddComponentMenu("AVPro Movie Capture/Audio/Ambisonic Source", 601)]
  12. public partial class AmbisonicSource : MonoBehaviour
  13. {
  14. [SerializeField] AmbisonicWavWriter _sink = null;
  15. [Tooltip("Listener is optional but allows positions to be calculated relative to a transform. This is useful if the listener is not located at 0,0,0.")]
  16. [SerializeField] Transform _listener = null;
  17. private Vector3 _position = Vector3.zero;
  18. private AmbisonicOrder _order;
  19. private AmbisonicChannelOrder _channelOrder;
  20. private AmbisonicNormalisation _normalisation;
  21. private System.IntPtr _sourceInstance = System.IntPtr.Zero;
  22. private int _activeSampleIndex = 0;
  23. private float[] _activeSamples = null;
  24. private Queue<float[]> _fullBuffers = new Queue<float[]>(8);
  25. private Queue<float[]> _emptyBuffers = new Queue<float[]>(8);
  26. void OnEnable()
  27. {
  28. AudioSource audioSource = this.GetComponent<AudioSource>();
  29. if (audioSource && audioSource.clip)
  30. {
  31. audioSource.PlayOneShot(audioSource.clip, 0f);
  32. }
  33. Debug.Assert(_sourceInstance == System.IntPtr.Zero);
  34. _sourceInstance = NativePlugin.AddAmbisonicSourceInstance(Ambisonic.MaxCoeffs);
  35. _position = this.transform.position;
  36. UpdateCoefficients();
  37. if (_sink)
  38. {
  39. _sink.AddSource(this);
  40. }
  41. }
  42. void OnDisable()
  43. {
  44. lock (this)
  45. {
  46. if (_sink)
  47. {
  48. _sink.RemoveSource(this);
  49. }
  50. if (_sourceInstance != System.IntPtr.Zero)
  51. {
  52. NativePlugin.RemoveAmbisonicSourceInstance(_sourceInstance);
  53. _sourceInstance = System.IntPtr.Zero;
  54. }
  55. }
  56. }
  57. internal void Setup(AmbisonicOrder order, AmbisonicChannelOrder channelOrder, AmbisonicNormalisation normalisation, int bufferCount)
  58. {
  59. Debug.Assert(bufferCount > 1 && bufferCount < 100);
  60. lock (this)
  61. {
  62. _order = order;
  63. _channelOrder = channelOrder;
  64. _normalisation = normalisation;
  65. int sampleCount = Ambisonic.GetCoeffCount(order) * AudioSettings.outputSampleRate / 10; // 1/10 second buffer
  66. _activeSampleIndex = 0;
  67. _activeSamples = null;
  68. _fullBuffers.Clear();
  69. _emptyBuffers.Clear();
  70. for (int i = 0; i < bufferCount; i++)
  71. {
  72. float[] buffer = new float[sampleCount];
  73. _emptyBuffers.Enqueue(buffer);
  74. }
  75. UpdateCoefficients();
  76. }
  77. }
  78. void OnDrawGizmos()
  79. {
  80. Gizmos.color = Color.green;
  81. Gizmos.DrawWireSphere(_position, 1.2f);
  82. if (_listener)
  83. {
  84. Gizmos.DrawLine(this.transform.position, _listener.position);
  85. }
  86. }
  87. void LateUpdate()
  88. {
  89. // Convert position relative to listener
  90. Vector3 p = this.transform.position;
  91. if (_listener)
  92. {
  93. p = (p - _listener.position);
  94. //Debug.Log(this.transform.parent.name + ": " + p + " " + _listener.position + " - " + this.transform.position);
  95. }
  96. // If the relative audiosource position has changed
  97. if (p != _position)
  98. {
  99. SetListenerRelativePosition(p);
  100. }
  101. }
  102. void SetListenerRelativePosition(Vector3 position)
  103. {
  104. // Optionally smooth out the motion
  105. //_position = Vector3.MoveTowards(_position, position, Time.deltaTime * 10f);
  106. _position = position;
  107. UpdateCoefficients();
  108. }
  109. void UpdateCoefficients()
  110. {
  111. Ambisonic.PolarCoord p = new Ambisonic.PolarCoord();
  112. p.FromCart(_position);
  113. lock (this)
  114. {
  115. float[] normaliseWeights = Ambisonic.GetNormalisationWeights(_normalisation);
  116. NativePlugin.UpdateAmbisonicWeights(_sourceInstance, p.azimuth, p.elevation, _order, _channelOrder, normaliseWeights);
  117. }
  118. }
  119. void OnAudioFilterRead(float[] samples, int channelCount)
  120. {
  121. lock (this)
  122. {
  123. int coeffCount = Ambisonic.GetCoeffCount(_order);
  124. if (_sink != null && coeffCount > 0)
  125. {
  126. int samplesOffset = 0;
  127. // While there are sample to process
  128. while (samplesOffset < samples.Length)
  129. {
  130. // If the pending buffer is full, move it to the full ist
  131. if (_activeSamples != null && _activeSamples.Length == _activeSampleIndex)
  132. {
  133. _fullBuffers.Enqueue(_activeSamples);
  134. _activeSamples = null;
  135. _activeSampleIndex = 0;
  136. }
  137. // Assign a new pending queue
  138. if (_activeSamples == null && _emptyBuffers.Count > 0)
  139. {
  140. _activeSamples = _emptyBuffers.Dequeue();
  141. }
  142. if (_activeSamples == null)
  143. {
  144. // Remaining samples are lost!
  145. break;
  146. }
  147. int remainingFrameCount = (samples.Length - samplesOffset) / channelCount;
  148. int generatedSampleCount = remainingFrameCount * coeffCount;
  149. int remainingSampleSpace = (_activeSamples.Length - _activeSampleIndex);
  150. int samplesToProcess = Mathf.Min(remainingSampleSpace, generatedSampleCount);
  151. // TODO: should we specify Floor/Ceil rounding behaviour?
  152. int framesToProcess = samplesToProcess / coeffCount;
  153. generatedSampleCount = framesToProcess * coeffCount;
  154. if (framesToProcess > 0)
  155. {
  156. NativePlugin.EncodeMonoToAmbisonic(_sourceInstance, samples, samplesOffset, framesToProcess, channelCount, _activeSamples, _activeSampleIndex, _activeSamples.Length, _order);
  157. _activeSampleIndex += generatedSampleCount;
  158. samplesOffset += framesToProcess * channelCount;
  159. }
  160. else
  161. {
  162. Debug.Log(coeffCount + " " + framesToProcess + " " + remainingSampleSpace + " >> " + samplesOffset + " / " + samples.Length);
  163. break;
  164. }
  165. }
  166. }
  167. }
  168. }
  169. internal void FlushBuffers()
  170. {
  171. lock (this)
  172. {
  173. _activeSampleIndex = 0;
  174. foreach (float[] buffer in _fullBuffers)
  175. {
  176. _emptyBuffers.Enqueue(buffer);
  177. }
  178. if (_activeSamples != null)
  179. {
  180. _emptyBuffers.Enqueue(_activeSamples);
  181. _activeSamples = null;
  182. }
  183. }
  184. }
  185. internal int GetFullBufferCount()
  186. {
  187. return _fullBuffers.Count;
  188. }
  189. internal void SendSamplesToSink(bool isAdditive, bool isDraining)
  190. {
  191. lock (this)
  192. {
  193. float[] samples = null;
  194. if (_fullBuffers.Count > 0)
  195. {
  196. // Send a full buffer
  197. samples = _fullBuffers.Dequeue();
  198. _sink.MixSamples(samples, samples.Length, isAdditive);
  199. }
  200. else if (isDraining)
  201. {
  202. // Send partial of the active buffer
  203. samples = _activeSamples;
  204. _sink.MixSamples(_activeSamples, _activeSampleIndex, isAdditive);
  205. _activeSampleIndex = 0;
  206. _activeSamples = null;
  207. }
  208. if (samples != null)
  209. {
  210. _emptyBuffers.Enqueue(samples);
  211. }
  212. }
  213. }
  214. }
  215. }