Files
2026-06-07 16:58:24 +01:00

181 lines
6.2 KiB
C#

using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim
{
[AddComponentMenu("")]
[DisallowMultipleComponent]
public class ClientSimSpatialAudioHelper : ONSPAudioSource
{
private const float EPS = 1e-3f;
private VRC_SpatialAudioSource _spatialAudioSource;
private AudioSource _audioSource;
private bool _useAudioSourceCurve;
private ONSPAudioSource _onsp;
private bool _forceUpdate = true;
private bool _updateONSPParams;
public static void InitializeAudio(VRC_SpatialAudioSource obj)
{
#if UNITY_EDITOR
// VRC_SpatialAudioSource executes in editor, meaning it will try to initialize even outside of playmode.
// This code is to prevent adding the ClientSim helper in these cases.
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode ||
UnityEditor.SceneManagement.EditorSceneManager.IsPreviewSceneObject(obj))
{
return;
}
#endif
ClientSimSpatialAudioHelper spatialAudio = obj.GetComponent<ClientSimSpatialAudioHelper>();
if (spatialAudio != null)
{
DestroyImmediate(spatialAudio);
}
spatialAudio = obj.gameObject.AddComponent<ClientSimSpatialAudioHelper>();
spatialAudio.PreventComponentFromSaving();
spatialAudio.SetSpatializer(obj);
}
private void SetSpatializer(VRC_SpatialAudioSource obj)
{
_spatialAudioSource = obj;
_audioSource = GetComponent<AudioSource>();
_onsp = this;
_forceUpdate = true;
UpdateSettings();
}
private void Start()
{
// Catch Helper not initialized.
if (_spatialAudioSource == null)
{
this.LogWarning($"Destroying uninitialized Helper. Object: {Tools.GetGameObjectPath(gameObject)}");
DestroyImmediate(this);
}
}
private void OnEnable()
{
// ONSP needs to reapply audio settings everytime the object is enabled.
_forceUpdate = true;
_updateONSPParams = true;
}
// Late update to help with testing
private void LateUpdate()
{
UpdateSettings();
}
private void UpdateSettings()
{
if (_spatialAudioSource == null)
{
_spatialAudioSource = GetComponent<VRC_SpatialAudioSource>();
if (_spatialAudioSource == null)
{
Destroy(this);
}
SetSpatializer(_spatialAudioSource);
return;
}
// Check if we need to make changes.
if (
_onsp.EnableSpatialization != _spatialAudioSource.EnableSpatialization ||
_onsp.Gain != _spatialAudioSource.Gain ||
_onsp.Near != _spatialAudioSource.Near ||
_onsp.Far != _spatialAudioSource.Far ||
_useAudioSourceCurve != _spatialAudioSource.UseAudioSourceVolumeCurve
) {
_forceUpdate = true;
_updateONSPParams = true;
}
_onsp.EnableSpatialization = _spatialAudioSource.EnableSpatialization;
_onsp.Gain = _spatialAudioSource.Gain;
_useAudioSourceCurve = _spatialAudioSource.UseAudioSourceVolumeCurve;
_onsp.Near = _spatialAudioSource.Near;
_onsp.Far = _spatialAudioSource.Far;
_onsp.VolumetricRadius = _spatialAudioSource.VolumetricRadius;
// In unity 2022 - updating ONSP params every frame can cause a logspam
// This is a workaround to only update when needed
if (_updateONSPParams)
{
_onsp.SetParameters(ref _audioSource);
_updateONSPParams = false;
}
if (!_onsp.EnableSpatialization)
{
return;
}
if (!_forceUpdate)
{
return;
}
_forceUpdate = false;
if (!_spatialAudioSource.UseAudioSourceVolumeCurve)
{
float near = _onsp.VolumetricRadius + _onsp.Near;
float far = _onsp.VolumetricRadius + Mathf.Max(near, _onsp.Far + EPS);
_audioSource.maxDistance = far;
CreateRolloffCurve(near, far);
CreateSpatialCurve(near, far);
}
}
// Create volume rolloff curve where Volumetric + near is volume 1, then 2^-x fall off to far.
private void CreateRolloffCurve(float near, float far)
{
_audioSource.rolloffMode = AudioRolloffMode.Custom;
AnimationCurve curve = new AnimationCurve();
curve.AddKey(new Keyframe(near, 1));
int max = 8;
for (int loc = 1; loc < max; ++loc)
{
float time = near + Mathf.Pow(2, loc - max) * (far - near);
float value = Mathf.Pow(2.2f, -loc);
curve.AddKey(new Keyframe(time, value));
}
curve.AddKey(new Keyframe(far, 0));
for (int i = 0; i < curve.length; ++i)
{
curve.SmoothTangents(i, 0);
}
_audioSource.SetCustomCurve(AudioSourceCurveType.CustomRolloff, curve);
}
// Create spatial blend curve so that it goes from (Setting) to 3d from min to max
private void CreateSpatialCurve(float near, float far)
{
AnimationCurve spatialCurve = new AnimationCurve();
spatialCurve.AddKey(0, _audioSource.spatialBlend);
spatialCurve.AddKey(_onsp.VolumetricRadius, _audioSource.spatialBlend);
Keyframe nearFrame = new Keyframe(near + EPS, 1);
nearFrame.outTangent = 0;
spatialCurve.AddKey(nearFrame);
Keyframe farFrame = new Keyframe(far, 1);
farFrame.inTangent = 0;
spatialCurve.AddKey(farFrame);
_audioSource.SetCustomCurve(AudioSourceCurveType.SpatialBlend, spatialCurve);
}
}
}