545 lines
20 KiB
C#
Executable File
545 lines
20 KiB
C#
Executable File
//
|
|
// Copyright 2017-2023 Valve Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
using AOT;
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using UnityEngine;
|
|
|
|
namespace SteamAudio
|
|
{
|
|
public enum DistanceAttenuationInput
|
|
{
|
|
CurveDriven,
|
|
PhysicsBased
|
|
}
|
|
|
|
public enum AirAbsorptionInput
|
|
{
|
|
SimulationDefined,
|
|
UserDefined
|
|
}
|
|
|
|
public enum DirectivityInput
|
|
{
|
|
SimulationDefined,
|
|
UserDefined
|
|
}
|
|
|
|
public enum OcclusionInput
|
|
{
|
|
SimulationDefined,
|
|
UserDefined
|
|
}
|
|
|
|
public enum TransmissionInput
|
|
{
|
|
SimulationDefined,
|
|
UserDefined
|
|
}
|
|
|
|
public enum ReflectionsType
|
|
{
|
|
Realtime,
|
|
BakedStaticSource,
|
|
BakedStaticListener
|
|
}
|
|
|
|
public struct AudioSourceAttenuationData
|
|
{
|
|
public AudioRolloffMode rolloffMode;
|
|
public float minDistance;
|
|
public float maxDistance;
|
|
public AnimationCurve curve;
|
|
}
|
|
|
|
[AddComponentMenu("Steam Audio/Steam Audio Source")]
|
|
public class SteamAudioSource : MonoBehaviour
|
|
{
|
|
[Header("HRTF Settings")]
|
|
public bool directBinaural = true;
|
|
public HRTFInterpolation interpolation = HRTFInterpolation.Nearest;
|
|
public bool perspectiveCorrection = false;
|
|
|
|
[Header("Attenuation Settings")]
|
|
public bool distanceAttenuation = false;
|
|
public DistanceAttenuationInput distanceAttenuationInput = DistanceAttenuationInput.CurveDriven;
|
|
public float distanceAttenuationValue = 1.0f;
|
|
public bool airAbsorption = false;
|
|
public AirAbsorptionInput airAbsorptionInput = AirAbsorptionInput.SimulationDefined;
|
|
[Range(0.0f, 1.0f)]
|
|
public float airAbsorptionLow = 1.0f;
|
|
[Range(0.0f, 1.0f)]
|
|
public float airAbsorptionMid = 1.0f;
|
|
[Range(0.0f, 1.0f)]
|
|
public float airAbsorptionHigh = 1.0f;
|
|
|
|
[Header("Directivity Settings")]
|
|
public bool directivity = false;
|
|
public DirectivityInput directivityInput = DirectivityInput.SimulationDefined;
|
|
[Range(0.0f, 1.0f)]
|
|
public float dipoleWeight = 0.0f;
|
|
[Range(0.0f, 4.0f)]
|
|
public float dipolePower = 0.0f;
|
|
[Range(0.0f, 1.0f)]
|
|
public float directivityValue = 1.0f;
|
|
|
|
[Header("Occlusion Settings")]
|
|
public bool occlusion = false;
|
|
public OcclusionInput occlusionInput = OcclusionInput.SimulationDefined;
|
|
public OcclusionType occlusionType = OcclusionType.Raycast;
|
|
[Range(0.0f, 4.0f)]
|
|
public float occlusionRadius = 1.0f;
|
|
[Range(1, 128)]
|
|
public int occlusionSamples = 16;
|
|
[Range(0.0f, 1.0f)]
|
|
public float occlusionValue = 1.0f;
|
|
public bool transmission = false;
|
|
public TransmissionType transmissionType = TransmissionType.FrequencyIndependent;
|
|
public TransmissionInput transmissionInput = TransmissionInput.SimulationDefined;
|
|
[Range(0.0f, 1.0f)]
|
|
public float transmissionLow = 1.0f;
|
|
[Range(0.0f, 1.0f)]
|
|
public float transmissionMid = 1.0f;
|
|
[Range(0.0f, 1.0f)]
|
|
public float transmissionHigh = 1.0f;
|
|
[Range(1, 8)]
|
|
public int maxTransmissionSurfaces = 1;
|
|
|
|
[Header("Direct Mix Settings")]
|
|
[Range(0.0f, 1.0f)]
|
|
public float directMixLevel = 1.0f;
|
|
|
|
[Header("Reflections Settings")]
|
|
public bool reflections = false;
|
|
public ReflectionsType reflectionsType = ReflectionsType.Realtime;
|
|
public bool useDistanceCurveForReflections = false;
|
|
public SteamAudioBakedSource currentBakedSource = null;
|
|
public IntPtr reflectionsIR = IntPtr.Zero;
|
|
public float reverbTimeLow = 0.0f;
|
|
public float reverbTimeMid = 0.0f;
|
|
public float reverbTimeHigh = 0.0f;
|
|
public float hybridReverbEQLow = 1.0f;
|
|
public float hybridReverbEQMid = 1.0f;
|
|
public float hybridReverbEQHigh = 1.0f;
|
|
public int hybridReverbDelay = 0;
|
|
public bool applyHRTFToReflections = false;
|
|
[Range(0.0f, 10.0f)]
|
|
public float reflectionsMixLevel = 1.0f;
|
|
|
|
[Header("Pathing Settings")]
|
|
public bool pathing = false;
|
|
public SteamAudioProbeBatch pathingProbeBatch = null;
|
|
public bool pathValidation = true;
|
|
public bool findAlternatePaths = true;
|
|
public float[] pathingEQ = new float[3] { 1.0f, 1.0f, 1.0f };
|
|
public float[] pathingSH = new float[16];
|
|
public bool applyHRTFToPathing = false;
|
|
[Range(0.0f, 10.0f)]
|
|
public float pathingMixLevel = 1.0f;
|
|
|
|
#if STEAMAUDIO_ENABLED
|
|
Simulator mSimulator = null;
|
|
Source mSource = null;
|
|
AudioEngineSource mAudioEngineSource = null;
|
|
UnityEngine.Vector3[] mSphereVertices = null;
|
|
UnityEngine.Vector3[] mDeformedSphereVertices = null;
|
|
Mesh mDeformedSphereMesh = null;
|
|
|
|
AudioSource mAudioSource = null;
|
|
AudioSourceAttenuationData mAttenuationData = new AudioSourceAttenuationData { };
|
|
DistanceAttenuationModel mCurveAttenuationModel = new DistanceAttenuationModel { };
|
|
GCHandle mThis;
|
|
SteamAudioSettings mSettings = null;
|
|
|
|
private void Awake()
|
|
{
|
|
mSimulator = SteamAudioManager.Simulator;
|
|
|
|
var settings = SteamAudioManager.GetSimulationSettings(false);
|
|
mSource = new Source(SteamAudioManager.Simulator, settings);
|
|
mSettings = SteamAudioSettings.Singleton;
|
|
|
|
mAudioEngineSource = AudioEngineSource.Create(mSettings.audioEngine);
|
|
if (mAudioEngineSource != null)
|
|
{
|
|
mAudioEngineSource.Initialize(gameObject);
|
|
mAudioEngineSource.UpdateParameters(this);
|
|
}
|
|
|
|
mAudioSource = GetComponent<AudioSource>();
|
|
|
|
mThis = GCHandle.Alloc(this);
|
|
|
|
if (mSettings.audioEngine == AudioEngineType.Unity &&
|
|
distanceAttenuation &&
|
|
distanceAttenuationInput == DistanceAttenuationInput.CurveDriven &&
|
|
reflections &&
|
|
useDistanceCurveForReflections)
|
|
{
|
|
mAttenuationData.rolloffMode = mAudioSource.rolloffMode;
|
|
mAttenuationData.minDistance = mAudioSource.minDistance;
|
|
mAttenuationData.maxDistance = mAudioSource.maxDistance;
|
|
mAttenuationData.curve = mAudioSource.GetCustomCurve(AudioSourceCurveType.CustomRolloff);
|
|
|
|
mCurveAttenuationModel.type = DistanceAttenuationModelType.Callback;
|
|
mCurveAttenuationModel.callback = EvaluateDistanceCurve;
|
|
mCurveAttenuationModel.userData = GCHandle.ToIntPtr(mThis);
|
|
mCurveAttenuationModel.dirty = Bool.False;
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (mAudioEngineSource != null)
|
|
{
|
|
mAudioEngineSource.UpdateParameters(this);
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (mAudioEngineSource != null)
|
|
{
|
|
mAudioEngineSource.Destroy();
|
|
mAudioEngineSource = null;
|
|
}
|
|
|
|
if (mSource != null)
|
|
{
|
|
mSource.Release();
|
|
mSource = null;
|
|
}
|
|
}
|
|
|
|
~SteamAudioSource()
|
|
{
|
|
mThis.Free();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
mSource.AddToSimulator(mSimulator);
|
|
SteamAudioManager.AddSource(this);
|
|
|
|
if (mAudioEngineSource != null)
|
|
{
|
|
mAudioEngineSource.UpdateParameters(this);
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
SteamAudioManager.RemoveSource(this);
|
|
mSource.RemoveFromSimulator(mSimulator);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (mAudioEngineSource != null)
|
|
{
|
|
mAudioEngineSource.UpdateParameters(this);
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
if (directivity && directivityInput == DirectivityInput.SimulationDefined && dipoleWeight > 0.0f)
|
|
{
|
|
if (mDeformedSphereMesh == null)
|
|
{
|
|
InitializeDeformedSphereMesh(32, 32);
|
|
}
|
|
|
|
DeformSphereMesh();
|
|
|
|
var oldColor = Gizmos.color;
|
|
Gizmos.color = Color.red;
|
|
Gizmos.DrawWireMesh(mDeformedSphereMesh, transform.position, transform.rotation);
|
|
Gizmos.color = oldColor;
|
|
}
|
|
}
|
|
|
|
public void SetInputs(SimulationFlags flags)
|
|
{
|
|
var listener = SteamAudioManager.GetSteamAudioListener();
|
|
|
|
var inputs = new SimulationInputs { };
|
|
inputs.source.origin = Common.ConvertVector(transform.position);
|
|
inputs.source.ahead = Common.ConvertVector(transform.forward);
|
|
inputs.source.up = Common.ConvertVector(transform.up);
|
|
inputs.source.right = Common.ConvertVector(transform.right);
|
|
|
|
if (mSettings.audioEngine == AudioEngineType.Unity &&
|
|
distanceAttenuation &&
|
|
distanceAttenuationInput == DistanceAttenuationInput.CurveDriven &&
|
|
reflections &&
|
|
useDistanceCurveForReflections)
|
|
{
|
|
inputs.distanceAttenuationModel = mCurveAttenuationModel;
|
|
}
|
|
else
|
|
{
|
|
inputs.distanceAttenuationModel.type = DistanceAttenuationModelType.Default;
|
|
}
|
|
|
|
inputs.airAbsorptionModel.type = AirAbsorptionModelType.Default;
|
|
inputs.directivity.dipoleWeight = dipoleWeight;
|
|
inputs.directivity.dipolePower = dipolePower;
|
|
inputs.occlusionType = occlusionType;
|
|
inputs.occlusionRadius = occlusionRadius;
|
|
inputs.numOcclusionSamples = occlusionSamples;
|
|
inputs.numTransmissionRays = maxTransmissionSurfaces;
|
|
inputs.reverbScaleLow = 1.0f;
|
|
inputs.reverbScaleMid = 1.0f;
|
|
inputs.reverbScaleHigh = 1.0f;
|
|
inputs.hybridReverbTransitionTime = mSettings.hybridReverbTransitionTime;
|
|
inputs.hybridReverbOverlapPercent = mSettings.hybridReverbOverlapPercent / 100.0f;
|
|
inputs.baked = (reflectionsType != ReflectionsType.Realtime) ? Bool.True : Bool.False;
|
|
inputs.pathingProbes = (pathingProbeBatch != null) ? pathingProbeBatch.GetProbeBatch() : IntPtr.Zero;
|
|
inputs.visRadius = mSettings.bakingVisibilityRadius;
|
|
inputs.visThreshold = mSettings.bakingVisibilityThreshold;
|
|
inputs.visRange = mSettings.bakingVisibilityRange;
|
|
inputs.pathingOrder = mSettings.bakingAmbisonicOrder;
|
|
inputs.enableValidation = pathValidation ? Bool.True : Bool.False;
|
|
inputs.findAlternatePaths = findAlternatePaths ? Bool.True : Bool.False;
|
|
|
|
if (reflectionsType == ReflectionsType.BakedStaticSource)
|
|
{
|
|
if (currentBakedSource != null)
|
|
{
|
|
inputs.bakedDataIdentifier = currentBakedSource.GetBakedDataIdentifier();
|
|
}
|
|
}
|
|
else if (reflectionsType == ReflectionsType.BakedStaticListener)
|
|
{
|
|
if (listener != null && listener.currentBakedListener != null)
|
|
{
|
|
inputs.bakedDataIdentifier = listener.currentBakedListener.GetBakedDataIdentifier();
|
|
}
|
|
}
|
|
|
|
inputs.flags = SimulationFlags.Direct;
|
|
if (reflections)
|
|
{
|
|
if ((reflectionsType == ReflectionsType.Realtime) ||
|
|
(reflectionsType == ReflectionsType.BakedStaticSource && currentBakedSource != null) ||
|
|
(reflectionsType == ReflectionsType.BakedStaticListener && listener != null && listener.currentBakedListener != null))
|
|
{
|
|
inputs.flags = inputs.flags | SimulationFlags.Reflections;
|
|
}
|
|
}
|
|
if (pathing)
|
|
{
|
|
if (pathingProbeBatch == null)
|
|
{
|
|
pathing = false;
|
|
Debug.LogWarningFormat("Pathing probe batch not set, disabling pathing for source {0}.", gameObject.name);
|
|
}
|
|
else
|
|
{
|
|
inputs.flags = inputs.flags | SimulationFlags.Pathing;
|
|
}
|
|
}
|
|
|
|
inputs.directFlags = 0;
|
|
if (distanceAttenuation)
|
|
inputs.directFlags = inputs.directFlags | DirectSimulationFlags.DistanceAttenuation;
|
|
if (airAbsorption)
|
|
inputs.directFlags = inputs.directFlags | DirectSimulationFlags.AirAbsorption;
|
|
if (directivity)
|
|
inputs.directFlags = inputs.directFlags | DirectSimulationFlags.Directivity;
|
|
if (occlusion)
|
|
inputs.directFlags = inputs.directFlags | DirectSimulationFlags.Occlusion;
|
|
if (transmission)
|
|
inputs.directFlags = inputs.directFlags | DirectSimulationFlags.Transmission;
|
|
|
|
mSource.SetInputs(flags, inputs);
|
|
}
|
|
|
|
public SimulationOutputs GetOutputs(SimulationFlags flags)
|
|
{
|
|
return mSource.GetOutputs(flags);
|
|
}
|
|
|
|
public Source GetSource()
|
|
{
|
|
return mSource;
|
|
}
|
|
|
|
public void UpdateOutputs(SimulationFlags flags)
|
|
{
|
|
var outputs = mSource.GetOutputs(flags);
|
|
|
|
if (SteamAudioSettings.Singleton.audioEngine == AudioEngineType.Unity &&
|
|
((flags & SimulationFlags.Direct) != 0))
|
|
{
|
|
if (distanceAttenuation && distanceAttenuationInput == DistanceAttenuationInput.PhysicsBased)
|
|
{
|
|
distanceAttenuationValue = outputs.direct.distanceAttenuation;
|
|
}
|
|
|
|
if (airAbsorption && airAbsorptionInput == AirAbsorptionInput.SimulationDefined)
|
|
{
|
|
airAbsorptionLow = outputs.direct.airAbsorptionLow;
|
|
airAbsorptionMid = outputs.direct.airAbsorptionMid;
|
|
airAbsorptionHigh = outputs.direct.airAbsorptionHigh;
|
|
}
|
|
|
|
if (directivity && directivityInput == DirectivityInput.SimulationDefined)
|
|
{
|
|
directivityValue = outputs.direct.directivity;
|
|
}
|
|
|
|
if (occlusion && occlusionInput == OcclusionInput.SimulationDefined)
|
|
{
|
|
occlusionValue = outputs.direct.occlusion;
|
|
}
|
|
|
|
if (transmission && transmissionInput == TransmissionInput.SimulationDefined)
|
|
{
|
|
transmissionLow = outputs.direct.transmissionLow;
|
|
transmissionMid = outputs.direct.transmissionMid;
|
|
transmissionHigh = outputs.direct.transmissionHigh;
|
|
}
|
|
}
|
|
|
|
if (pathing && ((flags & SimulationFlags.Pathing) != 0))
|
|
{
|
|
outputs.pathing.eqCoeffsLow = Mathf.Max(0.1f, outputs.pathing.eqCoeffsLow);
|
|
outputs.pathing.eqCoeffsMid = Mathf.Max(0.1f, outputs.pathing.eqCoeffsMid);
|
|
outputs.pathing.eqCoeffsHigh = Mathf.Max(0.1f, outputs.pathing.eqCoeffsHigh);
|
|
}
|
|
}
|
|
|
|
void InitializeDeformedSphereMesh(int nPhi, int nTheta)
|
|
{
|
|
var dPhi = (2.0f * Mathf.PI) / nPhi;
|
|
var dTheta = Mathf.PI / nTheta;
|
|
|
|
mSphereVertices = new UnityEngine.Vector3[nPhi * nTheta];
|
|
var index = 0;
|
|
for (var i = 0; i < nPhi; ++i)
|
|
{
|
|
var phi = i * dPhi;
|
|
for (var j = 0; j < nTheta; ++j)
|
|
{
|
|
var theta = (j * dTheta) - (0.5f * Mathf.PI);
|
|
|
|
var x = Mathf.Cos(theta) * Mathf.Sin(phi);
|
|
var y = Mathf.Sin(theta);
|
|
var z = Mathf.Cos(theta) * -Mathf.Cos(phi);
|
|
|
|
var vertex = new UnityEngine.Vector3(x, y, z);
|
|
|
|
mSphereVertices[index++] = vertex;
|
|
}
|
|
}
|
|
|
|
mDeformedSphereVertices = new UnityEngine.Vector3[nPhi * nTheta];
|
|
Array.Copy(mSphereVertices, mDeformedSphereVertices, mSphereVertices.Length);
|
|
|
|
var indices = new int[6 * nPhi * (nTheta - 1)];
|
|
index = 0;
|
|
for (var i = 0; i < nPhi; ++i)
|
|
{
|
|
for (var j = 0; j < nTheta - 1; ++j)
|
|
{
|
|
var i0 = i * nTheta + j;
|
|
var i1 = i * nTheta + (j + 1);
|
|
var i2 = ((i + 1) % nPhi) * nTheta + (j + 1);
|
|
var i3 = ((i + 1) % nPhi) * nTheta + j;
|
|
|
|
indices[index++] = i0;
|
|
indices[index++] = i1;
|
|
indices[index++] = i2;
|
|
indices[index++] = i0;
|
|
indices[index++] = i2;
|
|
indices[index++] = i3;
|
|
}
|
|
}
|
|
|
|
mDeformedSphereMesh = new Mesh();
|
|
mDeformedSphereMesh.vertices = mDeformedSphereVertices;
|
|
mDeformedSphereMesh.triangles = indices;
|
|
mDeformedSphereMesh.RecalculateNormals();
|
|
}
|
|
|
|
void DeformSphereMesh()
|
|
{
|
|
for (var i = 0; i < mSphereVertices.Length; ++i)
|
|
{
|
|
mDeformedSphereVertices[i] = DeformedVertex(mSphereVertices[i]);
|
|
}
|
|
|
|
mDeformedSphereMesh.vertices = mDeformedSphereVertices;
|
|
}
|
|
|
|
UnityEngine.Vector3 DeformedVertex(UnityEngine.Vector3 vertex)
|
|
{
|
|
var cosine = vertex.z;
|
|
var r = Mathf.Pow(Mathf.Abs((1.0f - dipoleWeight) + dipoleWeight * cosine), dipolePower);
|
|
var deformedVertex = vertex;
|
|
deformedVertex.Scale(new UnityEngine.Vector3(r, r, r));
|
|
return deformedVertex;
|
|
}
|
|
|
|
[MonoPInvokeCallback(typeof(DistanceAttenuationCallback))]
|
|
public static float EvaluateDistanceCurve(float distance, IntPtr userData)
|
|
{
|
|
var target = (SteamAudioSource) GCHandle.FromIntPtr(userData).Target;
|
|
|
|
var rMin = target.mAttenuationData.minDistance;
|
|
var rMax = target.mAttenuationData.maxDistance;
|
|
|
|
switch (target.mAttenuationData.rolloffMode)
|
|
{
|
|
case AudioRolloffMode.Logarithmic:
|
|
if (distance < rMin)
|
|
return 1.0f;
|
|
else if (distance < rMax)
|
|
return 0.0f;
|
|
else
|
|
return rMin / distance;
|
|
|
|
case AudioRolloffMode.Linear:
|
|
if (distance < rMin)
|
|
return 1.0f;
|
|
else if (distance > rMax)
|
|
return 0.0f;
|
|
else
|
|
return (rMax - distance) / (rMax - rMin);
|
|
|
|
case AudioRolloffMode.Custom:
|
|
#if UNITY_2018_1_OR_NEWER
|
|
return target.mAttenuationData.curve.Evaluate(distance / rMax);
|
|
#else
|
|
if (distance < rMin)
|
|
return 1.0f;
|
|
else if (distance > rMax)
|
|
return 0.0f;
|
|
else
|
|
return rMin / distance;
|
|
#endif
|
|
|
|
default:
|
|
return 0.0f;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|