// Copyright 2016 Google Inc. All rights reserved. // // 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 UnityEngine; using UnityEngine.Audio; using System.Collections; #pragma warning disable 0618 // Ignore GvrAudio* deprecation /// GVR soundfield component that allows playback of first-order ambisonic recordings. The audio /// sample should be in Ambix (ACN-SN3D) format. #if UNITY_2017_1_OR_NEWER [System.Obsolete("Please upgrade to Resonance Audio (https://developers.google.com/resonance-audio/migrate).")] #endif // UNITY_2017_1_OR_NEWER [AddComponentMenu("GoogleVR/Audio/GvrAudioSoundfield")] public class GvrAudioSoundfield : MonoBehaviour { /// Denotes whether the room effects should be bypassed. public bool bypassRoomEffects = true; /// Input gain in decibels. public float gainDb = 0.0f; /// Play source on awake. public bool playOnAwake = true; /// The default AudioClip to play. public AudioClip clip0102 { get { return soundfieldClip0102; } set { soundfieldClip0102 = value; if (audioSources != null && audioSources.Length > 0) { audioSources[0].clip = soundfieldClip0102; } } } [SerializeField] private AudioClip soundfieldClip0102 = null; public AudioClip clip0304 { get { return soundfieldClip0304; } set { soundfieldClip0304 = value; if (audioSources != null && audioSources.Length > 0) { audioSources[1].clip = soundfieldClip0304; } } } [SerializeField] private AudioClip soundfieldClip0304 = null; /// Is the clip playing right now (Read Only)? public bool isPlaying { get { if(audioSources != null && audioSources.Length > 0) { return audioSources[0].isPlaying; } return false; } } /// Is the audio clip looping? public bool loop { get { return soundfieldLoop; } set { soundfieldLoop = value; if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].loop = soundfieldLoop; } } } } [SerializeField] private bool soundfieldLoop = false; /// Un- / Mutes the soundfield. Mute sets the volume=0, Un-Mute restore the original volume. public bool mute { get { return soundfieldMute; } set { soundfieldMute = value; if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].mute = soundfieldMute; } } } } [SerializeField] private bool soundfieldMute = false; /// The pitch of the audio source. public float pitch { get { return soundfieldPitch; } set { soundfieldPitch = value; if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].pitch = soundfieldPitch; } } } } [SerializeField] [Range(-3.0f, 3.0f)] private float soundfieldPitch = 1.0f; /// Sets the priority of the soundfield. public int priority { get { return soundfieldPriority; } set { soundfieldPriority = value; if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].priority = soundfieldPriority; } } } } [SerializeField] [Range(0, 256)] private int soundfieldPriority = 32; /// Sets how much this soundfield is affected by 3D spatialization calculations /// (attenuation, doppler). public float spatialBlend { get { return soundfieldSpatialBlend; } set { soundfieldSpatialBlend = value; if (audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].spatialBlend = soundfieldSpatialBlend; } } } } [SerializeField] [Range(0.0f, 1.0f)] private float soundfieldSpatialBlend = 0.0f; /// Sets the Doppler scale for this soundfield. public float dopplerLevel { get { return soundfieldDopplerLevel; } set { soundfieldDopplerLevel = value; if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].dopplerLevel = soundfieldDopplerLevel; } } } } [SerializeField] [Range(0.0f, 5.0f)] private float soundfieldDopplerLevel = 0.0f; /// Playback position in seconds. public float time { get { if(audioSources != null && audioSources.Length > 0) { return audioSources[0].time; } return 0.0f; } set { if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].time = value; } } } } /// Playback position in PCM samples. public int timeSamples { get { if(audioSources != null && audioSources.Length > 0) { return audioSources[0].timeSamples; } return 0; } set { if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].timeSamples = value; } } } } /// The volume of the audio source (0.0 to 1.0). public float volume { get { return soundfieldVolume; } set { soundfieldVolume = value; if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].volume = soundfieldVolume; } } } } [SerializeField] [Range(0.0f, 1.0f)] private float soundfieldVolume = 1.0f; /// Volume rolloff model with respect to the distance. public AudioRolloffMode rolloffMode { get { return soundfieldRolloffMode; } set { soundfieldRolloffMode = value; if (audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].rolloffMode = soundfieldRolloffMode; if (rolloffMode == AudioRolloffMode.Custom) { // Custom rolloff is not supported, set the curve for no distance attenuation. audioSources[channelSet].SetCustomCurve( AudioSourceCurveType.CustomRolloff, AnimationCurve.Linear(soundfieldMinDistance, 1.0f, soundfieldMaxDistance, 1.0f)); } } } } } [SerializeField] private AudioRolloffMode soundfieldRolloffMode = AudioRolloffMode.Logarithmic; /// MaxDistance is the distance a sound stops attenuating at. public float maxDistance { get { return soundfieldMaxDistance; } set { soundfieldMaxDistance = Mathf.Clamp(value, soundfieldMinDistance + GvrAudio.distanceEpsilon, GvrAudio.maxDistanceLimit); if (audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].maxDistance = soundfieldMaxDistance; } } } } [SerializeField] private float soundfieldMaxDistance = 500.0f; /// Within the Min distance the GvrAudioSource will cease to grow louder in volume. public float minDistance { get { return soundfieldMinDistance; } set { soundfieldMinDistance = Mathf.Clamp(value, 0.0f, GvrAudio.minDistanceLimit); if (audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].minDistance = soundfieldMinDistance; } } } } [SerializeField] private float soundfieldMinDistance = 1.0f; // Unique source id. private int id = -1; // Unity audio sources per each soundfield channel set. private AudioSource[] audioSources = null; // Denotes whether the source is currently paused or not. private bool isPaused = false; void Awake () { #if UNITY_EDITOR && UNITY_2017_1_OR_NEWER Debug.LogWarningFormat(gameObject, "Game object '{0}' uses deprecated {1} component.\nPlease upgrade to Resonance Audio ({2}).", name, GetType().Name, "https://developers.google.com/resonance-audio/migrate"); #endif // UNITY_EDITOR && UNITY_2017_1_OR_NEWER // Route the source output to |GvrAudioMixer|. AudioMixer mixer = (Resources.Load("GvrAudioMixer") as AudioMixer); if(mixer == null) { Debug.LogError("GVRAudioMixer could not be found in Resources. Make sure that the GVR SDK" + "Unity package is imported properly."); return; } audioSources = new AudioSource[GvrAudio.numFoaChannels / 2]; for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { GameObject channelSetObject = new GameObject("Channel Set " + channelSet); channelSetObject.transform.parent = gameObject.transform; channelSetObject.transform.localPosition = Vector3.zero; channelSetObject.transform.localRotation = Quaternion.identity; channelSetObject.hideFlags = HideFlags.HideAndDontSave; audioSources[channelSet] = channelSetObject.AddComponent(); audioSources[channelSet].enabled = false; audioSources[channelSet].playOnAwake = false; audioSources[channelSet].bypassReverbZones = true; #if UNITY_5_5_OR_NEWER audioSources[channelSet].spatializePostEffects = true; #endif // UNITY_5_5_OR_NEWER audioSources[channelSet].outputAudioMixerGroup = mixer.FindMatchingGroups("Master")[0]; } OnValidate(); } void OnEnable () { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].enabled = true; } if (playOnAwake && !isPlaying && InitializeSoundfield()) { Play(); } } void Start () { if (playOnAwake && !isPlaying) { Play(); } } void OnDisable () { Stop(); for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].enabled = false; } } void OnDestroy () { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { Destroy(audioSources[channelSet].gameObject); } } void OnApplicationPause (bool pauseStatus) { if (pauseStatus) { Pause(); } else { UnPause(); } } void Update () { // Update soundfield. if (!isPlaying && !isPaused) { Stop(); } else { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].SetSpatializerFloat((int) GvrAudio.SpatializerData.Gain, GvrAudio.ConvertAmplitudeFromDb(gainDb)); audioSources[channelSet].SetSpatializerFloat((int) GvrAudio.SpatializerData.MinDistance, soundfieldMinDistance); } GvrAudio.UpdateAudioSoundfield(id, this); } } /// Pauses playing the clip. public void Pause () { if (audioSources != null) { isPaused = true; for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].Pause(); } } } /// Plays the clip. public void Play () { double dspTime = AudioSettings.dspTime; PlayScheduled(dspTime); } /// Plays the clip with a delay specified in seconds. public void PlayDelayed (float delay) { double delayedDspTime = AudioSettings.dspTime + (double)delay; PlayScheduled(delayedDspTime); } /// Plays the clip at a specific time on the absolute time-line that AudioSettings.dspTime reads /// from. public void PlayScheduled (double time) { if (audioSources != null && InitializeSoundfield()) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].PlayScheduled(time); } isPaused = false; } else { Debug.LogWarning ("GVR Audio soundfield not initialized. Audio playback not supported " + "until after Awake() and OnEnable(). Try calling from Start() instead."); } } /// Changes the time at which a sound that has already been scheduled to play will end. public void SetScheduledEndTime(double time) { if (audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].SetScheduledEndTime(time); } } } /// Changes the time at which a sound that has already been scheduled to play will start. public void SetScheduledStartTime(double time) { if (audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].SetScheduledStartTime(time); } } } /// Stops playing the clip. public void Stop () { if(audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].Stop(); } ShutdownSoundfield(); isPaused = false; } } /// Unpauses the paused playback. public void UnPause () { if (audioSources != null) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { audioSources[channelSet].UnPause(); } isPaused = false; } } // Initializes the source. private bool InitializeSoundfield () { if (id < 0) { id = GvrAudio.CreateAudioSoundfield(); if (id >= 0) { GvrAudio.UpdateAudioSoundfield(id, this); for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { InitializeChannelSet(audioSources[channelSet], channelSet); } } } return id >= 0; } // Shuts down the source. private void ShutdownSoundfield () { if (id >= 0) { for (int channelSet = 0; channelSet < audioSources.Length; ++channelSet) { ShutdownChannelSet(audioSources[channelSet], channelSet); } GvrAudio.DestroyAudioSource(id); id = -1; } } // Initializes given channel set of the soundfield. private void InitializeChannelSet(AudioSource source, int channelSet) { source.spatialize = true; source.SetSpatializerFloat((int) GvrAudio.SpatializerData.Type, (float) GvrAudio.SpatializerType.Soundfield); source.SetSpatializerFloat((int) GvrAudio.SpatializerData.NumChannels, (float) GvrAudio.numFoaChannels); source.SetSpatializerFloat((int) GvrAudio.SpatializerData.ChannelSet, (float) channelSet); source.SetSpatializerFloat((int) GvrAudio.SpatializerData.Gain, GvrAudio.ConvertAmplitudeFromDb(gainDb)); source.SetSpatializerFloat((int) GvrAudio.SpatializerData.MinDistance, soundfieldMinDistance); source.SetSpatializerFloat((int) GvrAudio.SpatializerData.ZeroOutput, 0.0f); // Soundfield id must be set after all the spatializer parameters, to ensure that the soundfield // is properly initialized before processing. source.SetSpatializerFloat((int) GvrAudio.SpatializerData.Id, (float) id); } // Shuts down given channel set of the soundfield. private void ShutdownChannelSet(AudioSource source, int channelSet) { source.SetSpatializerFloat((int) GvrAudio.SpatializerData.Id, -1.0f); // Ensure that the output is zeroed after shutdown. source.SetSpatializerFloat((int) GvrAudio.SpatializerData.ZeroOutput, 1.0f); source.spatialize = false; } void OnDidApplyAnimationProperties () { OnValidate(); } void OnValidate () { clip0102 = soundfieldClip0102; clip0304 = soundfieldClip0304; loop = soundfieldLoop; mute = soundfieldMute; pitch = soundfieldPitch; priority = soundfieldPriority; spatialBlend = soundfieldSpatialBlend; volume = soundfieldVolume; dopplerLevel = soundfieldDopplerLevel; minDistance = soundfieldMinDistance; maxDistance = soundfieldMaxDistance; rolloffMode = soundfieldRolloffMode; } } #pragma warning restore 0618 // Restore warnings