SteamAudio/Scripts/Runtime/SteamAudioSource.cs
2024-05-14 04:06:02 +02:00

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
}
}