using UnityEngine;
using System;
using System.Runtime.InteropServices;
#if UNITY_IOS || UNITY_TVOS || ENABLE_IL2CPP
using AOT;
#endif
//-----------------------------------------------------------------------------
// Copyright 2012-2022 RenderHeads Ltd. All rights reserved.
//-----------------------------------------------------------------------------
namespace RenderHeads.Media.AVProMovieCapture
{
public enum AmbisonicOrder : int
{
//Zero = 0,
First = 1,
Second = 2,
Third = 3,
}
public enum AmbisonicFormat
{
FuMa, // FuMa channel ordering and normalisation
ACN_SN3D, // ACN channel ordering with SN3D normalisation
}
public enum AmbisonicChannelOrder : int
{
FuMa,
ACN,
}
public enum AmbisonicNormalisation : int
{
FuMa,
SN3D,
}
public partial class NativePlugin
{
//////////////////////////////////////////////////////////////////////////
// Ambisonic
[DllImport(PluginName, EntryPoint="AVPMC_AddAmbisonicSourceInstance")]
public static extern IntPtr AddAmbisonicSourceInstance(int maxCoefficients);
[DllImport(PluginName, EntryPoint="AVPMC_RemoveAmbisonicSourceInstance")]
public static extern void RemoveAmbisonicSourceInstance(IntPtr instance);
[DllImport(PluginName, EntryPoint = "AVPMC_UpdateAmbisonicWeights")]
public static extern void UpdateAmbisonicWeights(IntPtr instance, float azimuth, float elevation, AmbisonicOrder order, AmbisonicChannelOrder channelOrder, float[] normalisationWeights);
[DllImport(PluginName, EntryPoint = "AVPMC_EncodeMonoToAmbisonic")]
public static extern void EncodeMonoToAmbisonic(IntPtr instance, float[] inSamples, int inSamplesOffset, int inFrameCount, int inChannelCount, float[] outSamples, int outSamplesOffset, int outSamplesLength, AmbisonicOrder order);
}
///
/// Internal helper class for encoding ambisonic audio
///
public static class Ambisonic
{
public const int MaxCoeffs = 16;
static float[] _weightsFuMa = null;
static float[] _weightsSN3D = null;
public static float[] GetNormalisationWeights(AmbisonicNormalisation normalisation)
{
return (normalisation == AmbisonicNormalisation.FuMa) ? _weightsFuMa : _weightsSN3D;
}
public static int GetCoeffCount(AmbisonicOrder order)
{
if (order == AmbisonicOrder.First) { return 4; }
else if (order == AmbisonicOrder.Second) { return 9; }
else if (order == AmbisonicOrder.Third) { return 16; }
return 0;
}
public static AmbisonicChannelOrder GetChannelOrder(AmbisonicFormat format)
{
return (format == AmbisonicFormat.FuMa) ? AmbisonicChannelOrder.FuMa : AmbisonicChannelOrder.ACN;
}
public static AmbisonicNormalisation GetNormalisation(AmbisonicFormat format)
{
return (format == AmbisonicFormat.FuMa) ? AmbisonicNormalisation.FuMa : AmbisonicNormalisation.SN3D;
}
static Ambisonic()
{
_weightsFuMa = BuildWeightsFuMa();
_weightsSN3D = BuildWeightsSN3D();
}
static float[] BuildWeightsFuMa()
{
float[] w = new float[MaxCoeffs];
w[0] = 1f / Mathf.Sqrt(2f);
w[1] = 1f;
w[2] = 1f;
w[3] = 1f;
w[4] = 1f;
w[5] = 2f / Mathf.Sqrt(3f);
w[6] = 2f / Mathf.Sqrt(3f);
w[7] = 2f / Mathf.Sqrt(3f);
w[8] = 2f / Mathf.Sqrt(3f);
w[9] = 1f;
w[10] = Mathf.Sqrt(45f / 32f);
w[11] = Mathf.Sqrt(45f / 32f);
w[12] = 3f / Mathf.Sqrt(5f);
w[13] = 3f / Mathf.Sqrt(5f);
w[14] = Mathf.Sqrt(8f / 5f);
w[15] = Mathf.Sqrt(8f / 5f);
return w;
}
// Returns N which is the same as the order
static int GetN(int acn)
{
return Mathf.FloorToInt(Mathf.Sqrt(acn));
}
// Returns M which is the signed delta offset from the middle of the pyramid
static int GetM(int acn)
{
int n = GetN(acn);
return acn - (n * n) - n;
}
static int Factorial(int x)
{
int result = 1;
for (int i = 2; i <= x; i++)
{
result *= i;
}
return result;
}
static float GetNormalisationSN3D(int acn)
{
int n = GetN(acn);
int m = GetM(acn);
return GetNormalisationSN3D(n, m);
}
static float GetNormalisationSN3D(int n, int m)
{
float dm = (m == 0) ? 1f : 0f;
float l1 = (2f - dm);
float a = Factorial(n - Mathf.Abs(m));
float b = Factorial(n + Mathf.Abs(m));
float l2 = a / b;
return Mathf.Sqrt(l1 * l2);
}
static float GetNormalisationN3D(int n, int m)
{
return GetNormalisationSN3D(n, m) * Mathf.Sqrt(2f * n + 1f);
}
static float[] BuildWeightsSN3D()
{
float[] w = new float[MaxCoeffs];
for (int acn = 0; acn < w.Length; acn++)
{
w[acn] = GetNormalisationSN3D(acn);
}
return w;
}
///
/// The coordinate system used in Ambisonics follows the right hand rule convention with
/// positive X pointing forwards,
/// positive Y pointing to the left and
/// positive Z pointing upwards
/// Horizontal angles run anticlockwise from due front and
/// vertical angles are positive above the horizontal, negative below.
///
internal struct PolarCoord
{
/// Azimuth (horizontal) angle in radians, 0..2PI
public float azimuth;
/// Elevation (vertical) angle in radians, -PI..PI
public float elevation;
//public float distance;
public void FromCart(Vector3 position)
{
// Convert from Unity's left-hand system to Ambisonics right-hand system
float x = position.z;
float y = -position.x;
float z = position.y;
// The azimuth angle is zero straight ahead and increases counter-clockwise.
azimuth = Mathf.Rad2Deg * Mathf.Atan2(y, x);
// Clamp
if (azimuth < 0f)
{
azimuth += 360f;
}
// The elevation angle is zero on the horizontal plane and positive in the upper hemisphere.
elevation = Mathf.Rad2Deg * Mathf.Atan2(z, Mathf.Sqrt(x * x + y * y));
elevation = Mathf.Clamp(elevation, -90f, 90f);
// NOTE: Distance is not currently used, but there may be scope in the future
//distance = Mathf.Sqrt( x * x + y * y + z * z );
// Back to radians
azimuth = Mathf.Deg2Rad * azimuth;
elevation = Mathf.Deg2Rad * elevation;
}
};
}
}