242 lines
6.6 KiB
C#
242 lines
6.6 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright 2012-2021 RenderHeads Ltd. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace RenderHeads.Media.AVProMovieCapture
|
|
{
|
|
/// <summary>
|
|
/// AudioSource should only have a single channel (mono)
|
|
/// </summary>
|
|
[AddComponentMenu("AVPro Movie Capture/Audio/Ambisonic Source", 301)]
|
|
public partial class AmbisonicSource : MonoBehaviour
|
|
{
|
|
[SerializeField] AmbisonicWavWriter _sink = null;
|
|
|
|
[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.")]
|
|
[SerializeField] Transform _listener = null;
|
|
|
|
private Vector3 _position = Vector3.zero;
|
|
private AmbisonicOrder _order;
|
|
private AmbisonicChannelOrder _channelOrder;
|
|
private AmbisonicNormalisation _normalisation;
|
|
private System.IntPtr _sourceInstance = System.IntPtr.Zero;
|
|
|
|
private int _activeSampleIndex = 0;
|
|
private float[] _activeSamples = null;
|
|
private Queue<float[]> _fullBuffers = new Queue<float[]>(8);
|
|
private Queue<float[]> _emptyBuffers = new Queue<float[]>(8);
|
|
|
|
void OnEnable()
|
|
{
|
|
AudioSource audioSource = this.GetComponent<AudioSource>();
|
|
if (audioSource && audioSource.clip)
|
|
{
|
|
audioSource.PlayOneShot(audioSource.clip, 0f);
|
|
}
|
|
|
|
Debug.Assert(_sourceInstance == System.IntPtr.Zero);
|
|
_sourceInstance = NativePlugin.AddAmbisonicSourceInstance(Ambisonic.MaxCoeffs);
|
|
|
|
_position = this.transform.position;
|
|
UpdateCoefficients();
|
|
if (_sink)
|
|
{
|
|
_sink.AddSource(this);
|
|
}
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
lock (this)
|
|
{
|
|
if (_sink)
|
|
{
|
|
_sink.RemoveSource(this);
|
|
}
|
|
|
|
if (_sourceInstance != System.IntPtr.Zero)
|
|
{
|
|
NativePlugin.RemoveAmbisonicSourceInstance(_sourceInstance);
|
|
_sourceInstance = System.IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void Setup(AmbisonicOrder order, AmbisonicChannelOrder channelOrder, AmbisonicNormalisation normalisation, int bufferCount)
|
|
{
|
|
Debug.Assert(bufferCount > 1 && bufferCount < 100);
|
|
lock (this)
|
|
{
|
|
_order = order;
|
|
_channelOrder = channelOrder;
|
|
_normalisation = normalisation;
|
|
int sampleCount = Ambisonic.GetCoeffCount(order) * AudioSettings.outputSampleRate / 10; // 1/10 second buffer
|
|
|
|
_activeSampleIndex = 0;
|
|
_activeSamples = null;
|
|
_fullBuffers.Clear();
|
|
_emptyBuffers.Clear();
|
|
for (int i = 0; i < bufferCount; i++)
|
|
{
|
|
float[] buffer = new float[sampleCount];
|
|
_emptyBuffers.Enqueue(buffer);
|
|
}
|
|
|
|
UpdateCoefficients();
|
|
}
|
|
}
|
|
|
|
void OnDrawGizmos()
|
|
{
|
|
Gizmos.color = Color.green;
|
|
Gizmos.DrawWireSphere(_position, 1.2f);
|
|
if (_listener)
|
|
{
|
|
Gizmos.DrawLine(this.transform.position, _listener.position);
|
|
}
|
|
}
|
|
|
|
void LateUpdate()
|
|
{
|
|
// Convert position relative to listener
|
|
Vector3 p = this.transform.position;
|
|
if (_listener)
|
|
{
|
|
p = (p - _listener.position);
|
|
//Debug.Log(this.transform.parent.name + ": " + p + " " + _listener.position + " - " + this.transform.position);
|
|
}
|
|
|
|
// If the relative audiosource position has changed
|
|
if (p != _position)
|
|
{
|
|
SetListenerRelativePosition(p);
|
|
}
|
|
}
|
|
|
|
void SetListenerRelativePosition(Vector3 position)
|
|
{
|
|
// Optionally smooth out the motion
|
|
//_position = Vector3.MoveTowards(_position, position, Time.deltaTime * 10f);
|
|
|
|
_position = position;
|
|
UpdateCoefficients();
|
|
}
|
|
|
|
void UpdateCoefficients()
|
|
{
|
|
Ambisonic.PolarCoord p = new Ambisonic.PolarCoord();
|
|
p.FromCart(_position);
|
|
|
|
lock (this)
|
|
{
|
|
float[] normaliseWeights = Ambisonic.GetNormalisationWeights(_normalisation);
|
|
NativePlugin.UpdateAmbisonicWeights(_sourceInstance, p.azimuth, p.elevation, _order, _channelOrder, normaliseWeights);
|
|
}
|
|
}
|
|
|
|
void OnAudioFilterRead(float[] samples, int channelCount)
|
|
{
|
|
lock (this)
|
|
{
|
|
int coeffCount = Ambisonic.GetCoeffCount(_order);
|
|
if (_sink != null && coeffCount > 0)
|
|
{
|
|
int samplesOffset = 0;
|
|
// While there are sample to process
|
|
while (samplesOffset < samples.Length)
|
|
{
|
|
// If the pending buffer is full, move it to the full ist
|
|
if (_activeSamples != null && _activeSamples.Length == _activeSampleIndex)
|
|
{
|
|
_fullBuffers.Enqueue(_activeSamples);
|
|
_activeSamples = null;
|
|
_activeSampleIndex = 0;
|
|
}
|
|
// Assign a new pending queue
|
|
if (_activeSamples == null && _emptyBuffers.Count > 0)
|
|
{
|
|
_activeSamples = _emptyBuffers.Dequeue();
|
|
}
|
|
if (_activeSamples == null)
|
|
{
|
|
// Remaining samples are lost!
|
|
break;
|
|
}
|
|
int remainingFrameCount = (samples.Length - samplesOffset) / channelCount;
|
|
int generatedSampleCount = remainingFrameCount * coeffCount;
|
|
int remainingSampleSpace = (_activeSamples.Length - _activeSampleIndex);
|
|
|
|
int samplesToProcess = Mathf.Min(remainingSampleSpace, generatedSampleCount);
|
|
// TODO: should we specify Floor/Ceil rounding behaviour?
|
|
int framesToProcess = samplesToProcess / coeffCount;
|
|
generatedSampleCount = framesToProcess * coeffCount;
|
|
|
|
if (framesToProcess > 0)
|
|
{
|
|
NativePlugin.EncodeMonoToAmbisonic(_sourceInstance, samples, samplesOffset, framesToProcess, channelCount, _activeSamples, _activeSampleIndex, _activeSamples.Length, _order);
|
|
_activeSampleIndex += generatedSampleCount;
|
|
samplesOffset += framesToProcess * channelCount;
|
|
}
|
|
else
|
|
{
|
|
Debug.Log(coeffCount + " " + framesToProcess + " " + remainingSampleSpace + " >> " + samplesOffset + " / " + samples.Length);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void FlushBuffers()
|
|
{
|
|
lock (this)
|
|
{
|
|
_activeSampleIndex = 0;
|
|
foreach (float[] buffer in _fullBuffers)
|
|
{
|
|
_emptyBuffers.Enqueue(buffer);
|
|
}
|
|
if (_activeSamples != null)
|
|
{
|
|
_emptyBuffers.Enqueue(_activeSamples);
|
|
_activeSamples = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal int GetFullBufferCount()
|
|
{
|
|
return _fullBuffers.Count;
|
|
}
|
|
|
|
internal void SendSamplesToSink(bool isAdditive, bool isDraining)
|
|
{
|
|
lock (this)
|
|
{
|
|
float[] samples = null;
|
|
if (_fullBuffers.Count > 0)
|
|
{
|
|
// Send a full buffer
|
|
samples = _fullBuffers.Dequeue();
|
|
_sink.MixSamples(samples, samples.Length, isAdditive);
|
|
}
|
|
else if (isDraining)
|
|
{
|
|
// Send partial of the active buffer
|
|
samples = _activeSamples;
|
|
_sink.MixSamples(_activeSamples, _activeSampleIndex, isAdditive);
|
|
_activeSampleIndex = 0;
|
|
_activeSamples = null;
|
|
}
|
|
|
|
if (samples != null)
|
|
{
|
|
_emptyBuffers.Enqueue(samples);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |