mixedreality/com.microsoft.mixedreality..../SDK/Features/Audio/Influencers/AudioInfluencerController.cs

259 lines
10 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityPhysics = UnityEngine.Physics;
namespace Microsoft.MixedReality.Toolkit.Audio
{
/// <summary>
/// Class which supports components implementing <see cref="Microsoft.MixedReality.Toolkit.Audio.IAudioInfluencer"/> being used with audio sources.
/// </summary>
/// <remarks>
/// <para>AudioInfluencerController requires an <see href="https://docs.unity3d.com/ScriptReference/AudioSource.html">AudioSource</see> component. If one is not attached, it will be added automatically.
/// Each sound playing game object needs to have an AudioInfluencerController attached in order to have its audio influenced.</para>
/// </remarks>
[RequireComponent(typeof(AudioSource))]
[DisallowMultipleComponent]
[AddComponentMenu("Scripts/MRTK/SDK/AudioInfluencerController")]
public class AudioInfluencerController : MonoBehaviour
{
/// <summary>
/// Frequency below the nominal range of human hearing.
/// </summary>
/// <remarks>
/// <para>This frequency can be used to set a high pass filter to allow all
/// audible frequencies through the filter.</para>
/// </remarks>
public static readonly float NeutralLowFrequency = 10.0f;
/// <summary>
/// Frequency above the nominal range of human hearing.
/// </summary>
/// <remarks>
/// <para>This frequency can be used to set a low pass filter to allow all
/// audible frequencies through the filter.</para>
/// </remarks>
public static readonly float NeutralHighFrequency = 22000.0f;
/// <summary>
/// Time, in seconds, between audio influence updates.
/// </summary>
/// <remarks>
/// <para>The UpdateInterval range is between 0.0 and 1.0, inclusive.
/// The default value is 0.25.
/// A value of 0.0f indicates that updates occur every frame.</para>
/// </remarks>
[Tooltip("Time, in seconds, between audio influence updates. 0 indicates to update every frame.")]
[Range(0.0f, 1.0f)]
[SerializeField]
private float updateInterval = 0.25f;
public float UpdateInterval
{
get { return updateInterval; }
set
{
updateInterval = Mathf.Clamp(value, 0.0f, 1.0f);
}
}
/// <summary>
/// Maximum distance, in meters, to look when attempting to find the user and any influencers.
/// </summary>
/// <remarks>
/// <para>The MaxDistance range is 1.0 to 50.0, inclusive.
/// The default value is 20.0.</para>
/// </remarks>
[Tooltip("Maximum distance, in meters, to look when attempting to find the user and any influencers.")]
[Range(1.0f, 50.0f)]
[SerializeField]
private float maxDistance = 20.0f;
public float MaxDistance
{
get { return maxDistance; }
set
{
maxDistance = Mathf.Clamp(value, 1.0f, 50.0f);
}
}
/// <summary>
/// Maximum number of objects that will be considered when looking for influencers.
/// Setting this value too high may have a negative impact on the performance of your experience.
/// </summary>
/// <remarks>
/// <para>MaxObjects can only be set in the Unity Inspector.
/// The MaxObjects range is 1 to 25, inclusive.
/// The default value is 10.</para>
/// </remarks>
[Tooltip("Maximum number of objects that will be considered when looking for influencers.")]
[Range(1, 25)]
[SerializeField]
private int maxObjects = 10;
/// <summary>
/// Time of last audio processing update.
/// </summary>
private DateTime lastUpdate = DateTime.MinValue;
/// <summary>
/// The source of the audio.
/// </summary>
[SerializeField]
private AudioSource audioSource;
/// <summary>
/// The initial volume level of the audio source.
/// </summary>
private float initialAudioSourceVolume;
/// <summary>
/// The hits returned by Physics.RaycastAll
/// </summary>
private RaycastHit[] hits;
/// <summary>
/// The collection of previously applied audio influencers.
/// </summary>
private List<IAudioInfluencer> previousInfluencers = new List<IAudioInfluencer>();
/// <summary>
/// Potential effects manipulated by an audio influencer.
/// </summary>
private AudioLowPassFilter lowPassFilter;
private AudioHighPassFilter highPassFilter;
private float nativeLowPassCutoffFrequency;
/// <summary>
/// Gets or sets the native low pass cutoff frequency for the
/// sound emitter.
/// </summary>
public float NativeLowPassCutoffFrequency
{
get { return nativeLowPassCutoffFrequency; }
set { value = nativeLowPassCutoffFrequency; }
}
private float nativeHighPassCutoffFrequency;
/// <summary>
/// Gets or sets the native high pass cutoff frequency for the
/// sound emitter.
/// </summary>
public float NativeHighPassCutoffFrequency
{
get { return nativeHighPassCutoffFrequency; }
set { value = nativeHighPassCutoffFrequency; }
}
private void Awake()
{
if (audioSource == null)
{
audioSource = GetComponent<AudioSource>();
}
initialAudioSourceVolume = audioSource.volume;
// Get optional filters (and initial values) that the sound designer / developer
// may have applied to this game object
lowPassFilter = gameObject.GetComponent<AudioLowPassFilter>();
nativeLowPassCutoffFrequency = (lowPassFilter != null) ? lowPassFilter.cutoffFrequency : NeutralHighFrequency;
highPassFilter = gameObject.GetComponent<AudioHighPassFilter>();
nativeHighPassCutoffFrequency = (highPassFilter != null) ? highPassFilter.cutoffFrequency : NeutralLowFrequency;
// Preallocate the array that will be used to collect RaycastHit structures.
hits = new RaycastHit[maxObjects];
}
private void Update()
{
DateTime now = DateTime.UtcNow;
// Audio influences are not updated every frame.
if ((UpdateInterval * 1000.0f) <= (now - lastUpdate).TotalMilliseconds)
{
audioSource.volume = initialAudioSourceVolume;
// Get the audio influencers that should apply to the audio source.
List<IAudioInfluencer> influencers = GetInfluencers();
for (int i = 0; i < influencers.Count; i++)
{
// Apply the influencer's effect.
influencers[i].ApplyEffect(gameObject);
}
// Find and remove the audio influencers that are to be removed from the audio source.
List<IAudioInfluencer> influencersToRemove = new List<IAudioInfluencer>();
for (int i = 0; i < previousInfluencers.Count; i++)
{
var audioInfluencer = previousInfluencers[i];
// Remove influencers that are
// no longer in line of sight,
// have been destroyed,
// or have been disabled
if (!influencers.Contains(audioInfluencer) ||
!audioInfluencer.TryGetMonoBehaviour(out MonoBehaviour mbPrev) ||
!mbPrev.isActiveAndEnabled)
{
influencersToRemove.Add(audioInfluencer);
}
}
RemoveInfluencers(influencersToRemove);
previousInfluencers = influencers;
lastUpdate = now;
}
}
/// <summary>
/// Removes the effects applied by specified audio influencers.
/// </summary>
/// <param name="influencers">Collection of IAudioInfluencer objects on which to remove the effect.</param>
private void RemoveInfluencers(List<IAudioInfluencer> influencers)
{
for (int i = 0; i < influencers.Count; i++)
{
influencers[i].RemoveEffect(gameObject);
}
}
/// <summary>
/// Finds the IAudioInfluencer objects that are to be applied to the audio source.
/// </summary>
/// <returns>Collection of IAudioInfluencers between the user and the game object.</returns>
private List<IAudioInfluencer> GetInfluencers()
{
List<IAudioInfluencer> influencers = new List<IAudioInfluencer>();
Transform cameraTransform = CameraCache.Main.transform;
// Influencers take effect only when between the emitter and the user.
// Perform a raycast from the user toward the object.
Vector3 direction = (gameObject.transform.position - cameraTransform.position).normalized;
float distance = Vector3.Distance(cameraTransform.position, gameObject.transform.position);
int count = UnityPhysics.RaycastNonAlloc(cameraTransform.position,
direction,
hits,
distance,
UnityPhysics.DefaultRaycastLayers,
QueryTriggerInteraction.Ignore);
for (int i = 0; i < count; i++)
{
IAudioInfluencer influencer = hits[i].collider.gameObject.GetComponentInParent<IAudioInfluencer>();
if (influencer != null)
{
influencers.Add(influencer);
}
}
return influencers;
}
}
}