├── .gitignore ├── CODE_OF_CONDUCT.md ├── Editor.meta ├── Editor ├── AudioBankEditor.cs ├── AudioBankEditor.cs.meta ├── AudioGraph.cs ├── AudioGraph.cs.meta ├── AudioProfiler.cs ├── AudioProfiler.cs.meta ├── ProfilerEvent.cs ├── ProfilerEvent.cs.meta ├── com.microsoft.audiomanagerforunity.editor.asmdef └── com.microsoft.audiomanagerforunity.editor.asmdef.meta ├── LICENSE ├── README.md ├── Runtime.meta ├── Runtime ├── ActiveEvent.cs ├── ActiveEvent.cs.meta ├── ActiveParameter.cs ├── ActiveParameter.cs.meta ├── AudioBank.cs ├── AudioBank.cs.meta ├── AudioBlendContainer.cs ├── AudioBlendContainer.cs.meta ├── AudioBlendFile.cs ├── AudioBlendFile.cs.meta ├── AudioDebugMessage.cs ├── AudioDebugMessage.cs.meta ├── AudioDelay.cs ├── AudioDelay.cs.meta ├── AudioEvent.cs ├── AudioEvent.cs.meta ├── AudioEventParameter.cs ├── AudioEventParameter.cs.meta ├── AudioEventRouter.cs ├── AudioEventRouter.cs.meta ├── AudioFile.cs ├── AudioFile.cs.meta ├── AudioLanguageSelector.cs ├── AudioLanguageSelector.cs.meta ├── AudioManager.cs ├── AudioManager.cs.meta ├── AudioNode.cs ├── AudioNode.cs.meta ├── AudioNodeInput.cs ├── AudioNodeInput.cs.meta ├── AudioNodeOutput.cs ├── AudioNodeOutput.cs.meta ├── AudioNullFile.cs ├── AudioNullFile.cs.meta ├── AudioOutput.cs ├── AudioOutput.cs.meta ├── AudioParameter.cs ├── AudioParameter.cs.meta ├── AudioRandomSelector.cs ├── AudioRandomSelector.cs.meta ├── AudioSequenceSelector.cs ├── AudioSequenceSelector.cs.meta ├── AudioSnapshotTransition.cs ├── AudioSnapshotTransition.cs.meta ├── AudioSwitch.cs ├── AudioSwitch.cs.meta ├── AudioSwitchSelector.cs ├── AudioSwitchSelector.cs.meta ├── AudioVoiceFile.cs ├── AudioVoiceFile.cs.meta ├── com.microsoft.audiomanagerforunity.asmdef └── com.microsoft.audiomanagerforunity.asmdef.meta ├── SECURITY.md ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75615a22f364a6a428fa11391fa09468 3 | folderAsset: yes 4 | timeCreated: 1518573276 5 | licenseType: Pro 6 | DefaultImporter: 7 | externalObjects: {} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Editor/AudioBankEditor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | 7 | namespace Microsoft.MixedReality.Toolkit.Audio 8 | { 9 | /// 10 | /// Override inspector for quick editing in the graph 11 | /// 12 | [CustomEditor(typeof(AudioBank))] 13 | public class AudioBankEditor : UnityEditor.Editor 14 | { 15 | /// 16 | /// AudioBank to edit in the graph 17 | /// 18 | private AudioBank myTarget; 19 | 20 | /// 21 | /// Set reference for AudioBank to pass to graph window 22 | /// 23 | private void OnEnable() 24 | { 25 | this.myTarget = (AudioBank)target; 26 | } 27 | 28 | /// 29 | /// Display a button to open the bank in the graph 30 | /// 31 | public override void OnInspectorGUI() 32 | { 33 | if (GUILayout.Button("Open in Graph")) 34 | { 35 | AudioGraph.OpenAudioGraph(this.myTarget); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Editor/AudioBankEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a7cdb4a4d16a9fe46bec7464501de464 3 | timeCreated: 1518661436 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/AudioGraph.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEditor; 5 | using UnityEngine; 6 | using UnityEngine.Audio; 7 | using System.Collections.Generic; 8 | 9 | 10 | namespace Microsoft.MixedReality.Toolkit.Audio 11 | { 12 | public class AudioGraph : EditorWindow 13 | { 14 | /// 15 | /// The AudioBank currently being edited 16 | /// 17 | private AudioBank audioBank; 18 | /// 19 | /// The AudioEvent currently being edited 20 | /// 21 | private AudioEvent selectedEvent; 22 | /// 23 | /// The AudioNode currently selected 24 | /// 25 | private AudioNode selectedNode; 26 | /// 27 | /// The node output being connected to an input 28 | /// 29 | private AudioNodeOutput selectedOutput; 30 | /// 31 | /// The rectangle defining the space where the list of events are drawn 32 | /// 33 | private Rect eventListRect = new Rect(0, 20, 200, 400); 34 | /// 35 | /// The rectangle defining the space where the event's properties are drawn 36 | /// 37 | private Rect eventPropertyRect = new Rect(200, 20, 240, 150); 38 | /// 39 | /// The rectangle defining the space where the parameters are drawn 40 | /// 41 | private Rect parameterListRect = new Rect(0, 20, 300, 400); 42 | /// 43 | /// Current position of the scroll box for the list of AudioEvents 44 | /// 45 | private Vector2 eventListScrollPosition = new Vector2(); 46 | private Vector2 batchEventsScrollPosition = new Vector2(); 47 | /// 48 | /// Current position of the scroll view for the list of the current AudioEvent's properties 49 | /// 50 | private Vector2 eventPropertiesScrollPosition = new Vector2(); 51 | /// 52 | /// Current position of the scroll view for the list of parameters 53 | /// 54 | private Vector2 parameterListScrollPosition = new Vector2(); 55 | /// 56 | /// The position of the mouse on the graph to calculate panning 57 | /// 58 | private Vector3 lastMousePos; 59 | /// 60 | /// The horizontal offset of the graph canvas in the window 61 | /// 62 | private float panX = 0; 63 | /// 64 | /// The verical offset of the graph canvas in the window 65 | /// 66 | private float panY = 0; 67 | /// 68 | /// Whether mouse movement should be used to calculate panning the graph 69 | /// 70 | private bool panGraph = false; 71 | /// 72 | /// Whether the graph has been panned since the right mouse button was clicked 73 | /// 74 | private bool hasPanned = false; 75 | /// 76 | /// Whether the right mouse button has been clicked 77 | /// 78 | private bool rightButtonClicked = false; 79 | private bool leftButtonDown = false; 80 | /// 81 | /// The runtime event used to preview sounds in the graph 82 | /// 83 | private ActiveEvent previewEvent; 84 | /// 85 | /// Selection for which editor is currently being used 86 | /// 87 | private EditorTypes editorType = EditorTypes.Events; 88 | /// 89 | /// Names for the available editor types 90 | /// 91 | private readonly string[] editorTypeNames = { "Events", "Parameters", "Batch Edit", "Switches" }; 92 | /// 93 | /// The color to display a button for an event that is not currently being edited 94 | /// 95 | private Color unselectedButton = new Color(0.8f, 0.8f, 0.8f, 1); 96 | private bool batchSetBus = false; 97 | private AudioMixerGroup batchBus; 98 | private bool batchSetMinVol = false; 99 | private float batchMinVol = 1; 100 | private bool batchSetMaxVol = false; 101 | private float batchMaxVol = 1; 102 | private bool batchSetMinPitch = false; 103 | private float batchMinPitch = 1; 104 | private bool batchSetMaxPitch = false; 105 | private float batchMaxPitch = 1; 106 | private bool batchSetLoop = false; 107 | private bool batchLoop = false; 108 | private bool batchSetSpatialBlend = false; 109 | private float batchSpatialBlend = 0; 110 | private bool batchSetHRTF = false; 111 | private bool batchHRTF = false; 112 | private bool batchSetMaxDistance = false; 113 | private float batchMaxDistance = 10; 114 | private bool batchSetAttenuation = false; 115 | private AnimationCurve batchAttenuation = new AnimationCurve(); 116 | private bool batchSetDoppler = false; 117 | private float batchDoppler = 1; 118 | private bool[] batchEventSelection = new bool[0]; 119 | 120 | /// 121 | /// The size in pixels of the node canvas for the graph 122 | /// 123 | private const float CANVAS_SIZE = 20000; 124 | /// 125 | /// The distance in pixels between nodes when added via script 126 | /// 127 | private const float HORIZONTAL_NODE_OFFSET = 400; 128 | 129 | /// 130 | /// List of available editors for an AudioBank 131 | /// 132 | public enum EditorTypes 133 | { 134 | Events, 135 | Parameters, 136 | BatchEdit, 137 | Switches 138 | } 139 | 140 | /// 141 | /// Display the graph window 142 | /// 143 | [MenuItem("Window/Audio Graph")] 144 | private static void OpenAudioGraph() 145 | { 146 | AudioGraph graph = GetWindow(); 147 | graph.titleContent = new GUIContent("Audio Graph"); 148 | graph.Show(); 149 | } 150 | 151 | /// 152 | /// Display the graph window and automatically open an existing AudioBank 153 | /// 154 | /// 155 | public static void OpenAudioGraph(AudioBank bankToLoad) 156 | { 157 | AudioGraph graph = GetWindow(); 158 | graph.titleContent = new GUIContent("Audio Graph"); 159 | graph.audioBank = bankToLoad; 160 | graph.Show(); 161 | } 162 | 163 | private void Update() 164 | { 165 | Repaint(); 166 | 167 | if (AudioManager.Languages == null || AudioManager.Languages.Length == 0) 168 | { 169 | AudioManager.UpdateLanguages(); 170 | } 171 | } 172 | 173 | private void OnGUI() 174 | { 175 | if (this.audioBank != null) 176 | { 177 | DragAndDrop.visualMode = DragAndDropVisualMode.Generic; 178 | Event e = Event.current; 179 | if (e.type == EventType.DragExited) 180 | { 181 | HandleDrag(e); 182 | return; 183 | } 184 | } 185 | else 186 | { 187 | DragAndDrop.visualMode = DragAndDropVisualMode.Rejected; 188 | } 189 | 190 | GUILayout.BeginHorizontal(EditorStyles.toolbar); 191 | this.editorType = (EditorTypes)GUILayout.Toolbar((int)this.editorType, this.editorTypeNames, EditorStyles.toolbarButton); 192 | GUILayout.FlexibleSpace(); 193 | if (GUILayout.Button("Actions", EditorStyles.toolbarDropDown)) 194 | { 195 | GenericMenu newNodeMenu = new GenericMenu(); 196 | newNodeMenu.AddItem(new GUIContent("Add Event"), false, AddEvent); 197 | newNodeMenu.AddItem(new GUIContent("Delete Event"), false, ConfirmDeleteEvent); 198 | newNodeMenu.AddItem(new GUIContent("Preview Event"), false, PreviewEvent); 199 | newNodeMenu.AddItem(new GUIContent("Stop Preview"), false, StopPreview); 200 | newNodeMenu.AddItem(new GUIContent("Sort Events"), false, SortEventList); 201 | newNodeMenu.ShowAsContext(); 202 | } 203 | GUILayout.EndHorizontal(); 204 | 205 | switch (this.editorType) 206 | { 207 | case EditorTypes.Events: 208 | if (this.selectedEvent != null) 209 | { 210 | GetInput(); 211 | 212 | DrawEventNodes(this.selectedEvent); 213 | 214 | DrawEventProperties(this.selectedEvent); 215 | } 216 | DrawEventList(); 217 | break; 218 | case EditorTypes.Parameters: 219 | DrawParameterList(); 220 | break; 221 | case EditorTypes.Switches: 222 | DrawSwitchList(); 223 | break; 224 | case EditorTypes.BatchEdit: 225 | DrawBatchEditor(); 226 | break; 227 | } 228 | } 229 | 230 | #region Drawing 231 | 232 | /// 233 | /// Display the list of buttons to select an event 234 | /// 235 | private void DrawEventList() 236 | { 237 | this.eventListRect.height = this.position.height; 238 | GUILayout.BeginArea(this.eventListRect); 239 | this.eventListScrollPosition = EditorGUILayout.BeginScrollView(this.eventListScrollPosition); 240 | this.audioBank = EditorGUILayout.ObjectField(this.audioBank, typeof(AudioBank), false) as AudioBank; 241 | 242 | if (this.audioBank == null) 243 | { 244 | EditorGUILayout.EndScrollView(); 245 | GUILayout.EndArea(); 246 | return; 247 | } 248 | 249 | if (this.audioBank.EditorEvents != null) 250 | { 251 | for (int i = 0; i < this.audioBank.EditorEvents.Count; i++) 252 | { 253 | AudioEvent tempEvent = this.audioBank.EditorEvents[i]; 254 | if (tempEvent == null) 255 | { 256 | continue; 257 | } 258 | 259 | if (this.selectedEvent == tempEvent) 260 | { 261 | GUI.color = Color.white; 262 | } 263 | else 264 | { 265 | GUI.color = this.unselectedButton; 266 | } 267 | 268 | if (GUILayout.Button(tempEvent.name)) 269 | { 270 | SelectEvent(tempEvent); 271 | } 272 | 273 | GUI.color = Color.white; 274 | } 275 | } 276 | 277 | EditorGUILayout.EndScrollView(); 278 | GUILayout.EndArea(); 279 | } 280 | 281 | /// 282 | /// Display the all of the parameters in the bank 283 | /// 284 | private void DrawParameterList() 285 | { 286 | this.parameterListRect.height = this.position.height / 2; 287 | GUILayout.BeginArea(this.parameterListRect); 288 | this.parameterListScrollPosition = EditorGUILayout.BeginScrollView(this.parameterListScrollPosition); 289 | 290 | if (this.audioBank == null) 291 | { 292 | EditorGUILayout.EndScrollView(); 293 | GUILayout.EndArea(); 294 | return; 295 | } 296 | 297 | if (GUILayout.Button("Add Parameter")) 298 | { 299 | this.audioBank.AddParameter(); 300 | } 301 | 302 | if (this.audioBank.EditorParameters != null) 303 | { 304 | for (int i = 0; i < this.audioBank.EditorParameters.Count; i++) 305 | { 306 | AudioParameter tempParameter = this.audioBank.EditorParameters[i]; 307 | if (tempParameter == null) 308 | { 309 | continue; 310 | } 311 | 312 | tempParameter.DrawParameterEditor(); 313 | if (GUILayout.Button("Delete Parameter")) 314 | { 315 | this.audioBank.DeleteParameter(tempParameter); 316 | } 317 | EditorGUILayout.Separator(); 318 | } 319 | } 320 | EditorGUILayout.EndScrollView(); 321 | GUILayout.EndArea(); 322 | } 323 | 324 | private void DrawSwitchList() 325 | { 326 | this.parameterListRect.height = this.position.height / 2; 327 | GUILayout.BeginArea(this.parameterListRect); 328 | this.parameterListScrollPosition = EditorGUILayout.BeginScrollView(this.parameterListScrollPosition); 329 | 330 | if (this.audioBank == null) 331 | { 332 | EditorGUILayout.EndScrollView(); 333 | GUILayout.EndArea(); 334 | return; 335 | } 336 | 337 | if (GUILayout.Button("Add Switch")) 338 | { 339 | this.audioBank.AddSwitch(); 340 | } 341 | 342 | if (this.audioBank.EditorSwitches != null) 343 | { 344 | for (int i = 0; i < this.audioBank.EditorSwitches.Count; i++) 345 | { 346 | AudioSwitch tempSwitch = this.audioBank.EditorSwitches[i]; 347 | if (tempSwitch == null) 348 | { 349 | continue; 350 | } 351 | 352 | tempSwitch.DrawSwitchEditor(); 353 | if (GUILayout.Button("Delete Switch")) 354 | { 355 | this.audioBank.DeleteSwitch(tempSwitch); 356 | } 357 | EditorGUILayout.Separator(); 358 | } 359 | } 360 | EditorGUILayout.EndScrollView(); 361 | GUILayout.EndArea(); 362 | } 363 | 364 | /// 365 | /// Draw the interface for editing multiple AudioEvents 366 | /// 367 | private void DrawBatchEditor() 368 | { 369 | GUILayout.BeginHorizontal(); 370 | this.batchSetBus = EditorGUILayout.Toggle("Set Mixer Group", this.batchSetBus); 371 | this.batchBus = EditorGUILayout.ObjectField("Mixer Group", this.batchBus, typeof(AudioMixerGroup), false) as AudioMixerGroup; 372 | GUILayout.EndHorizontal(); 373 | GUILayout.BeginHorizontal(); 374 | this.batchSetMinVol = EditorGUILayout.Toggle("Set Min Vol", this.batchSetMinVol); 375 | this.batchMinVol = EditorGUILayout.FloatField("New Min Vol", this.batchMinVol); 376 | GUILayout.EndHorizontal(); 377 | GUILayout.BeginHorizontal(); 378 | this.batchSetMaxVol = EditorGUILayout.Toggle("Set Max Vol", this.batchSetMaxVol); 379 | this.batchMaxVol = EditorGUILayout.FloatField("New Max Vol", this.batchMaxVol); 380 | GUILayout.EndHorizontal(); 381 | GUILayout.BeginHorizontal(); 382 | this.batchSetMinPitch = EditorGUILayout.Toggle("Set Min Pitch", this.batchSetMinPitch); 383 | this.batchMinPitch = EditorGUILayout.FloatField("New Min Pitch", this.batchMinPitch); 384 | GUILayout.EndHorizontal(); 385 | GUILayout.BeginHorizontal(); 386 | this.batchSetMaxPitch = EditorGUILayout.Toggle("Set Max Pitch", this.batchSetMinPitch); 387 | this.batchMaxPitch = EditorGUILayout.FloatField("New Max Pitch", this.batchMaxPitch); 388 | GUILayout.EndHorizontal(); 389 | GUILayout.BeginHorizontal(); 390 | this.batchSetLoop = EditorGUILayout.Toggle("Set Loop", this.batchSetLoop); 391 | this.batchLoop = EditorGUILayout.Toggle("New Loop", this.batchLoop); 392 | GUILayout.EndHorizontal(); 393 | GUILayout.BeginHorizontal(); 394 | this.batchSetSpatialBlend = EditorGUILayout.Toggle("Set Spatial Blend", this.batchSetSpatialBlend); 395 | this.batchSpatialBlend = EditorGUILayout.FloatField("New Spatial Blend", this.batchSpatialBlend); 396 | GUILayout.EndHorizontal(); 397 | GUILayout.BeginHorizontal(); 398 | this.batchSetHRTF = EditorGUILayout.Toggle("Set HRTF", this.batchSetHRTF); 399 | this.batchHRTF = EditorGUILayout.Toggle("New HRTF", this.batchHRTF); 400 | GUILayout.EndHorizontal(); 401 | GUILayout.BeginHorizontal(); 402 | this.batchSetMaxDistance = EditorGUILayout.Toggle("Set Max Distance", this.batchSetMaxDistance); 403 | this.batchMaxDistance = EditorGUILayout.FloatField("New Max Distance", this.batchMaxDistance); 404 | GUILayout.EndHorizontal(); 405 | GUILayout.BeginHorizontal(); 406 | this.batchSetAttenuation = EditorGUILayout.Toggle("Set Attenuation Curve", this.batchSetAttenuation); 407 | this.batchAttenuation = EditorGUILayout.CurveField("New Attenuation Curve", this.batchAttenuation); 408 | GUILayout.EndHorizontal(); 409 | GUILayout.BeginHorizontal(); 410 | this.batchSetDoppler = EditorGUILayout.Toggle("Set Doppler", this.batchSetDoppler); 411 | this.batchDoppler = EditorGUILayout.FloatField("New Doppler", this.batchDoppler); 412 | GUILayout.EndHorizontal(); 413 | GUILayout.BeginHorizontal(); 414 | if (GUILayout.Button("Populate Events")) 415 | { 416 | int eventNum = this.audioBank.AudioEvents.Count; 417 | this.batchEventSelection = new bool[eventNum]; 418 | } 419 | if (GUILayout.Button("Select All Events")) 420 | { 421 | for (int i = 0; i < this.batchEventSelection.Length; i++) 422 | { 423 | this.batchEventSelection[i] = true; 424 | } 425 | } 426 | if (GUILayout.Button("Deselect All Events")) 427 | { 428 | for (int i = 0; i < this.batchEventSelection.Length; i++) 429 | { 430 | this.batchEventSelection[i] = false; 431 | } 432 | } 433 | GUILayout.EndHorizontal(); 434 | DrawEventSelection(); 435 | } 436 | 437 | private void DrawEventSelection() 438 | { 439 | if (GUILayout.Button("Run Batch Edit")) 440 | { 441 | RunBatchEdit(); 442 | } 443 | this.batchEventsScrollPosition = EditorGUILayout.BeginScrollView(this.batchEventsScrollPosition); 444 | for (int i = 0; i < this.batchEventSelection.Length; i++) 445 | { 446 | this.batchEventSelection[i] = EditorGUILayout.Toggle(this.audioBank.AudioEvents[i].name, this.batchEventSelection[i]); 447 | } 448 | EditorGUILayout.EndScrollView(); 449 | } 450 | 451 | /// 452 | /// Display the properties of the specified AudioEvent 453 | /// 454 | /// The event to display the properties for 455 | private void DrawEventProperties(AudioEvent audioEvent) 456 | { 457 | GUILayout.BeginArea(this.eventPropertyRect); 458 | this.eventPropertiesScrollPosition = EditorGUILayout.BeginScrollView(this.eventPropertiesScrollPosition); 459 | audioEvent.name = EditorGUILayout.TextField("Event Name", audioEvent.name); 460 | audioEvent.InstanceLimit = EditorGUILayout.IntField("Instance limit", audioEvent.InstanceLimit); 461 | audioEvent.FadeIn = EditorGUILayout.FloatField("Fade In", audioEvent.FadeIn); 462 | audioEvent.FadeOut = EditorGUILayout.FloatField("Fade Out", audioEvent.FadeOut); 463 | audioEvent.Group = EditorGUILayout.IntField("Group", audioEvent.Group); 464 | 465 | audioEvent.DrawParameters(); 466 | EditorGUILayout.EndScrollView(); 467 | GUILayout.EndArea(); 468 | } 469 | 470 | /// 471 | /// Display the nodes for an AuidoEvent on the graph 472 | /// 473 | /// The audio event to display the nodes for 474 | private void DrawEventNodes(AudioEvent audioEvent) 475 | { 476 | if (audioEvent == null) 477 | { 478 | return; 479 | } 480 | 481 | if (audioEvent.EditorNodes == null) 482 | { 483 | return; 484 | } 485 | 486 | GUI.BeginGroup(new Rect(this.panX, this.panY, CANVAS_SIZE, CANVAS_SIZE)); 487 | BeginWindows(); 488 | for (int i = 0; i < audioEvent.EditorNodes.Count; i++) 489 | { 490 | AudioNode currentNode = audioEvent.EditorNodes[i]; 491 | currentNode.DrawNode(i); 492 | } 493 | EndWindows(); 494 | GUI.EndGroup(); 495 | } 496 | 497 | #endregion 498 | 499 | /// 500 | /// Create a new event and select it in the graph 501 | /// 502 | private void AddEvent() 503 | { 504 | AudioEvent newEvent = this.audioBank.AddEvent(new Vector2(CANVAS_SIZE / 2, CANVAS_SIZE / 2)); 505 | SelectEvent(newEvent); 506 | } 507 | 508 | /// 509 | /// Display a confirmation dialog and delete the currently-selected event if confirmed 510 | /// 511 | private void ConfirmDeleteEvent() 512 | { 513 | if (EditorUtility.DisplayDialog("Confrim Event Deletion", "Delete event \"" + this.selectedEvent.name + "\"?", "Yes", "No")) 514 | { 515 | this.audioBank.DeleteEvent(this.selectedEvent); 516 | } 517 | } 518 | 519 | /// 520 | /// Select an event to display in the graph 521 | /// 522 | /// The audio event to select and display in the graph 523 | private void SelectEvent(AudioEvent selection) 524 | { 525 | this.selectedEvent = selection; 526 | Rect output = this.selectedEvent.Output.NodeRect; 527 | this.panX = -output.x + (this.position.width - output.width - 20); 528 | this.panY = -output.y + (this.position.height / 2); 529 | } 530 | 531 | /// 532 | /// Play the currently-selected event in the scene 533 | /// 534 | private void PreviewEvent() 535 | { 536 | if (!Application.isPlaying) 537 | { 538 | EditorUtility.DisplayDialog("Can't Preview Audio Event", "Editor must be in play mode to preview events", "OK"); 539 | return; 540 | } 541 | 542 | if (this.previewEvent != null) 543 | { 544 | this.previewEvent.Stop(); 545 | } 546 | 547 | GameObject tempEmitter = new GameObject("Preview_" + this.selectedEvent.name); 548 | this.previewEvent = AudioManager.PlayEvent(this.selectedEvent, tempEmitter); 549 | Destroy(tempEmitter, this.previewEvent.EstimatedRemainingTime + 1); 550 | } 551 | 552 | /// 553 | /// Stop the currently-playing event that was previewed from the graph 554 | /// 555 | private void StopPreview() 556 | { 557 | if (this.previewEvent != null) 558 | { 559 | this.previewEvent.Stop(); 560 | } 561 | } 562 | 563 | /// 564 | /// Process mouse clicks and call appropriate editor functions 565 | /// 566 | private void GetInput() 567 | { 568 | if (this.selectedEvent == null) 569 | { 570 | return; 571 | } 572 | 573 | Event e = Event.current; 574 | switch (e.type) 575 | { 576 | case EventType.MouseDown: 577 | this.selectedNode = GetNodeAtPosition(e.mousePosition); 578 | Selection.activeObject = this.selectedNode; 579 | if (e.button == 0) 580 | { 581 | HandleLeftClick(e); 582 | } 583 | else if (e.button == 1) 584 | { 585 | HandleRightClick(e); 586 | } 587 | break; 588 | case EventType.MouseUp: 589 | HandleMouseUp(e); 590 | break; 591 | default: 592 | HandleMouseMovement(e); 593 | break; 594 | } 595 | } 596 | 597 | private void RunBatchEdit() 598 | { 599 | List audioEvents = this.audioBank.AudioEvents; 600 | for (int i = 0; i < audioEvents.Count; i++) 601 | { 602 | if (this.batchEventSelection[i]) 603 | { 604 | SetBatchProperties(audioEvents[i]); 605 | } 606 | } 607 | } 608 | 609 | private void SetBatchProperties(AudioEvent batchEvent) 610 | { 611 | AudioOutput op = batchEvent.Output; 612 | if (this.batchSetBus) 613 | { 614 | op.mixerGroup = this.batchBus; 615 | } 616 | if (this.batchSetMinVol) 617 | { 618 | op.MinVolume = this.batchMinVol; 619 | } 620 | if (this.batchSetMaxVol) 621 | { 622 | op.MaxVolume = this.batchMaxVol; 623 | } 624 | if (this.batchSetMinPitch) 625 | { 626 | op.MinPitch = this.batchMinPitch; 627 | } 628 | if (this.batchSetMaxPitch) 629 | { 630 | op.MaxPitch = this.batchMaxPitch; 631 | } 632 | if (this.batchSetLoop) 633 | { 634 | op.loop = this.batchLoop; 635 | } 636 | if (this.batchSetSpatialBlend) 637 | { 638 | op.spatialBlend = this.batchSpatialBlend; 639 | } 640 | if (this.batchSetHRTF) 641 | { 642 | op.HRTF = this.batchHRTF; 643 | } 644 | if (this.batchSetMaxDistance) 645 | { 646 | op.MaxDistance = this.batchMaxDistance; 647 | } 648 | if (this.batchSetAttenuation) 649 | { 650 | op.attenuationCurve = this.batchAttenuation; 651 | } 652 | if (this.batchSetDoppler) 653 | { 654 | op.dopplerLevel = this.batchDoppler; 655 | } 656 | } 657 | 658 | private void SortEventList() 659 | { 660 | if (this.audioBank != null) 661 | { 662 | this.audioBank.SortEvents(); 663 | } 664 | } 665 | 666 | #region Mouse 667 | 668 | /// 669 | /// Perform necessary actions for the left mouse button being pushed this frame 670 | /// 671 | /// The input event handled in Unity 672 | private void HandleLeftClick(Event e) 673 | { 674 | this.leftButtonDown = true; 675 | this.rightButtonClicked = false; 676 | this.selectedOutput = GetOutputAtPosition(e.mousePosition); 677 | 678 | this.selectedNode = GetNodeAtPosition(e.mousePosition); 679 | } 680 | 681 | /// 682 | /// Perform necessary actions for the right mouse button being pushed this frame 683 | /// 684 | /// The input event handled by Unity 685 | private void HandleRightClick(Event e) 686 | { 687 | this.rightButtonClicked = true; 688 | if (this.selectedOutput == null) 689 | { 690 | this.panGraph = true; 691 | this.lastMousePos = e.mousePosition; 692 | } 693 | } 694 | 695 | /// 696 | /// Perform necessary actions for the a mouse button being released this frame 697 | /// 698 | /// The input event handled by Unity 699 | private void HandleMouseUp(Event e) 700 | { 701 | this.leftButtonDown = false; 702 | if (this.rightButtonClicked && !this.hasPanned) 703 | { 704 | this.selectedNode = GetNodeAtPosition(e.mousePosition); 705 | this.selectedOutput = GetOutputAtPosition(e.mousePosition); 706 | AudioNodeInput tempInput = GetInputAtPosition(e.mousePosition); 707 | 708 | if (tempInput != null) 709 | { 710 | InputContextMenu(e.mousePosition); 711 | } 712 | else if (this.selectedOutput != null) 713 | { 714 | OutputContextMenu(e.mousePosition); 715 | } 716 | else if (this.selectedNode == null) 717 | { 718 | CanvasContextMenu(e.mousePosition); 719 | } 720 | else 721 | { 722 | ModifyNodeContextMenu(e.mousePosition); 723 | } 724 | } 725 | else 726 | { 727 | if (this.selectedOutput != null) 728 | { 729 | AudioNodeInput hoverInput = GetInputAtPosition(e.mousePosition); 730 | if (hoverInput != null) 731 | { 732 | hoverInput.AddConnection(this.selectedOutput); 733 | } 734 | } 735 | } 736 | 737 | this.panGraph = false; 738 | this.hasPanned = false; 739 | this.selectedOutput = null; 740 | this.rightButtonClicked = false; 741 | this.leftButtonDown = false; 742 | } 743 | 744 | /// 745 | /// Perform necessary actions for the mouse moving while a button is held down 746 | /// 747 | /// The input event handled by Unity 748 | private void HandleMouseMovement(Event e) 749 | { 750 | if (leftButtonDown && selectedNode != null && e.shift) 751 | { 752 | Vector3 tempMove = new Vector2(lastMousePos.x, lastMousePos.y) - e.mousePosition; 753 | AudioNodeOutput[] outputs = selectedNode.Input.ConnectedNodes; 754 | for (int i = 0; i < outputs.Length; i++) 755 | { 756 | outputs[i].ParentNode.MoveBy(tempMove); 757 | } 758 | 759 | } 760 | 761 | if (this.selectedOutput == null) 762 | { 763 | if (this.panGraph && this.selectedNode == null) 764 | { 765 | if (Vector2.Distance(e.mousePosition, this.lastMousePos) > 0) 766 | { 767 | this.hasPanned = true; 768 | this.panX += (e.mousePosition.x - this.lastMousePos.x); 769 | this.panY += (e.mousePosition.y - this.lastMousePos.y); 770 | } 771 | } 772 | } 773 | else 774 | { 775 | AudioNode.DrawCurve(ConvertToLocalPosition(this.selectedOutput.Center), e.mousePosition); 776 | } 777 | 778 | this.lastMousePos = e.mousePosition; 779 | } 780 | 781 | /// 782 | /// Drag and drop functionality for adding clips 783 | /// 784 | /// The input event handled by Unity 785 | private void HandleDrag(Event e) 786 | { 787 | int clipSelection = EditorUtility.DisplayDialogComplex("Add Clips to Audio Bank", "How should these clips be added?", "In current event", "In separate events", "Cancel"); 788 | 789 | DragAndDrop.AcceptDrag(); 790 | List clips = new List(); 791 | for (int i = 0; i < DragAndDrop.objectReferences.Length; i++) 792 | { 793 | AudioClip tempClip = DragAndDrop.objectReferences[i] as AudioClip; 794 | if (tempClip != null) 795 | { 796 | clips.Add(tempClip); 797 | } 798 | else 799 | { 800 | Debug.Log("NULL CLIP"); 801 | } 802 | } 803 | 804 | switch (clipSelection) 805 | { 806 | case 0: 807 | //Add clips to current event 808 | AddNodes(clips); 809 | break; 810 | case 1: 811 | //Add a new event for each clip 812 | AddEvents(clips); 813 | break; 814 | case 2: 815 | break; 816 | } 817 | } 818 | 819 | #endregion 820 | 821 | /// 822 | /// Create a context menu for a node's input connector 823 | /// 824 | /// The position on the graph to place the menu 825 | private void InputContextMenu(Vector2 position) 826 | { 827 | GenericMenu newNodeMenu = new GenericMenu(); 828 | newNodeMenu.AddItem(new GUIContent("Clear Connections"), false, ClearInput, position); 829 | newNodeMenu.AddItem(new GUIContent("Sort Connections"), false, SortInput, position); 830 | newNodeMenu.ShowAsContext(); 831 | } 832 | 833 | /// 834 | /// Create a context menu for a node's output connector 835 | /// 836 | /// The position on the graph to place the menu 837 | private void OutputContextMenu(Vector2 position) 838 | { 839 | GenericMenu newNodeMenu = new GenericMenu(); 840 | newNodeMenu.AddItem(new GUIContent("Clear Connections"), false, ClearOutput, position); 841 | newNodeMenu.ShowAsContext(); 842 | } 843 | 844 | /// 845 | /// Create a context menu on a blank space in the graph 846 | /// 847 | /// The position on the graph to place the menu 848 | private void CanvasContextMenu(Vector2 position) 849 | { 850 | GenericMenu newNodeMenu = new GenericMenu(); 851 | newNodeMenu.AddItem(new GUIContent("Add Audio File"), false, AddNodeAtPosition, position); 852 | newNodeMenu.AddItem(new GUIContent("Add Voice File"), false, AddNodeAtPosition, position); 853 | newNodeMenu.AddItem(new GUIContent("Add Random Selector"), false, AddNodeAtPosition, position); 854 | newNodeMenu.AddItem(new GUIContent("Add Sequence Selector"), false, AddNodeAtPosition, position); 855 | newNodeMenu.AddItem(new GUIContent("Add Language Selector"), false, AddNodeAtPosition, position); 856 | newNodeMenu.AddItem(new GUIContent("Add Switch Selector"), false, AddNodeAtPosition, position); 857 | newNodeMenu.AddItem(new GUIContent("Add Blend Container"), false, AddNodeAtPosition, position); 858 | newNodeMenu.AddItem(new GUIContent("Add Blend File"), false, AddNodeAtPosition, position); 859 | newNodeMenu.AddItem(new GUIContent("Add Snapshot Transition"), false, AddNodeAtPosition, position); 860 | newNodeMenu.AddItem(new GUIContent("Add Delay"), false, AddNodeAtPosition, position); 861 | newNodeMenu.AddItem(new GUIContent("Add Debug Message"), false, AddNodeAtPosition, position); 862 | newNodeMenu.AddItem(new GUIContent("Add Null File"), false, AddNodeAtPosition, position); 863 | newNodeMenu.AddItem(new GUIContent("Set Output Position"), false, SetOutputPosition, position); 864 | newNodeMenu.ShowAsContext(); 865 | } 866 | 867 | /// 868 | /// Create a context menu on a node 869 | /// 870 | /// The position on the graph to place the menu 871 | private void ModifyNodeContextMenu(Vector2 position) 872 | { 873 | GenericMenu newNodeMenu = new GenericMenu(); 874 | newNodeMenu.AddItem(new GUIContent("Delete Node"), false, RemoveNodeAtPosition, position); 875 | newNodeMenu.ShowAsContext(); 876 | } 877 | 878 | #region Nodes 879 | 880 | /// 881 | /// Place the Output node at the specified position 882 | /// 883 | /// The position at which to place the Output node 884 | private void SetOutputPosition(object positionObject) 885 | { 886 | Vector2 newPosition = (Vector2)positionObject; 887 | newPosition = ConvertToGlobalPosition(newPosition); 888 | this.selectedEvent.Output.SetPosition(newPosition); 889 | } 890 | 891 | /// 892 | /// Remove a node from the current AudioEvent and delete it from the asset 893 | /// 894 | /// The position of the object to delete 895 | private void RemoveNodeAtPosition(object positionObject) 896 | { 897 | AudioNode tempNode = GetNodeAtPosition((Vector2)positionObject); 898 | this.selectedEvent.DeleteNode(tempNode); 899 | EditorUtility.SetDirty(this.selectedEvent); 900 | } 901 | 902 | /// 903 | /// Add a new node on the graph and add it to the current event 904 | /// 905 | /// The AudioNode type to create 906 | /// The position at which to place the new node 907 | public void AddNodeAtPosition(object positionObject) where T : AudioNode 908 | { 909 | Vector2 position = (Vector2)positionObject; 910 | T tempNode = ScriptableObject.CreateInstance(); 911 | AssetDatabase.AddObjectToAsset(tempNode, this.selectedEvent); 912 | tempNode.InitializeNode(ConvertToGlobalPosition(position)); 913 | this.selectedEvent.AddNode(tempNode); 914 | EditorUtility.SetDirty(this.selectedEvent); 915 | } 916 | 917 | /// 918 | /// Add a new node via script 919 | /// 920 | /// The AudioNode type to create 921 | /// The position at which to place the new node 922 | /// The added AudioNode 923 | private T AddNodeAtPosition(Vector2 position) where T : AudioNode 924 | { 925 | T tempNode = ScriptableObject.CreateInstance(); 926 | AssetDatabase.AddObjectToAsset(tempNode, this.selectedEvent); 927 | tempNode.InitializeNode(position); 928 | this.selectedEvent.AddNode(tempNode); 929 | EditorUtility.SetDirty(this.selectedEvent); 930 | return tempNode; 931 | } 932 | 933 | /// 934 | /// Add a new AudioFile node for each AudioClip in the list to the current AudioEvent 935 | /// 936 | /// The list of AudioClips to add to the event 937 | private void AddNodes(List clips) 938 | { 939 | Vector2 tempPos = this.selectedEvent.Output.NodeRect.position; 940 | tempPos.x -= HORIZONTAL_NODE_OFFSET; 941 | 942 | for (int i = 0; i < clips.Count; i++) 943 | { 944 | AudioFile tempNode = AddNodeAtPosition(tempPos); 945 | tempNode.File = clips[i]; 946 | tempPos.y += 120; 947 | } 948 | } 949 | 950 | /// 951 | /// Add an AudioEvent to the bank for each clip from the list 952 | /// 953 | /// The list of AudioClips to add 954 | private void AddEvents(List clips) 955 | { 956 | for (int i = 0; i < clips.Count; i++) 957 | { 958 | AudioEvent newEvent = this.audioBank.AddEvent(new Vector2(CANVAS_SIZE / 2, CANVAS_SIZE / 2)); 959 | Vector3 position = newEvent.Output.NodeRect.position; 960 | position.x -= HORIZONTAL_NODE_OFFSET; 961 | AudioFile tempNode = ScriptableObject.CreateInstance(); 962 | AssetDatabase.AddObjectToAsset(tempNode, newEvent); 963 | tempNode.InitializeNode(position); 964 | tempNode.File = clips[i]; 965 | newEvent.AddNode(tempNode); 966 | newEvent.name = clips[i].name; 967 | newEvent.Output.Input.AddConnection(tempNode.Output); 968 | } 969 | 970 | EditorUtility.SetDirty(this.audioBank); 971 | } 972 | 973 | /// 974 | /// Remove all connections from an input connector 975 | /// 976 | /// The position of the input connector 977 | public void ClearInput(object positionObject) 978 | { 979 | AudioNodeInput tempInput = GetInputAtPosition((Vector2)positionObject); 980 | tempInput.RemoveAllConnections(); 981 | } 982 | 983 | /// 984 | /// Arrange the order of connections for an input connector 985 | /// 986 | /// The position of the input connector 987 | public void SortInput(object positionObject) 988 | { 989 | AudioNodeInput tempInput = GetInputAtPosition((Vector2)positionObject); 990 | tempInput.SortConnections(); 991 | } 992 | 993 | /// 994 | /// Remove all connections from an output connector 995 | /// 996 | /// 997 | public void ClearOutput(object positionObject) 998 | { 999 | AudioNodeOutput tempOutput = GetOutputAtPosition((Vector2)positionObject); 1000 | for (int i = 0; i < this.selectedEvent.EditorNodes.Count; i++) 1001 | { 1002 | AudioNode tempNode = this.selectedEvent.EditorNodes[i]; 1003 | if (tempNode.Input != null) 1004 | { 1005 | tempNode.Input.RemoveConnection(tempOutput); 1006 | } 1007 | } 1008 | } 1009 | 1010 | /// 1011 | /// Find a node that overlaps a position on the graph 1012 | /// 1013 | /// The position on the graph to check against the nodes 1014 | /// The first node found that occupies the specified position or null 1015 | private AudioNode GetNodeAtPosition(Vector2 position) 1016 | { 1017 | if (this.selectedEvent == null) 1018 | { 1019 | return null; 1020 | } 1021 | 1022 | position = ConvertToGlobalPosition(position); 1023 | 1024 | for (int i = 0; i < this.selectedEvent.EditorNodes.Count; i++) 1025 | { 1026 | AudioNode tempNode = this.selectedEvent.EditorNodes[i]; 1027 | if (tempNode.NodeRect.Contains(position)) 1028 | { 1029 | return tempNode; 1030 | } 1031 | } 1032 | 1033 | return null; 1034 | } 1035 | 1036 | /// 1037 | /// Find an input connector that overlaps the position on the graph 1038 | /// 1039 | /// The position on the graph to test against all input connectors 1040 | /// The first input connector found that occupies the specified position or null 1041 | private AudioNodeInput GetInputAtPosition(Vector2 position) 1042 | { 1043 | if (this.selectedEvent == null) 1044 | { 1045 | return null; 1046 | } 1047 | 1048 | position = ConvertToGlobalPosition(position); 1049 | 1050 | for (int i = 0; i < this.selectedEvent.EditorNodes.Count; i++) 1051 | { 1052 | AudioNode tempNode = this.selectedEvent.EditorNodes[i]; 1053 | if (tempNode.Input != null && tempNode.Input.Window.Contains(position)) 1054 | { 1055 | return tempNode.Input; 1056 | } 1057 | } 1058 | 1059 | return null; 1060 | } 1061 | 1062 | /// 1063 | /// Find an output connector that overlaps the position on the graph 1064 | /// 1065 | /// The position on the graph to test against all output connectors 1066 | /// The first output connector found that occupies the specified position or null 1067 | private AudioNodeOutput GetOutputAtPosition(Vector2 position) 1068 | { 1069 | position = ConvertToGlobalPosition(position); 1070 | if (this.selectedEvent == null) 1071 | { 1072 | Debug.LogWarning("Tried to get output with no selected event"); 1073 | return null; 1074 | } 1075 | 1076 | for (int i = 0; i < this.selectedEvent.EditorNodes.Count; i++) 1077 | { 1078 | AudioNode tempNode = this.selectedEvent.EditorNodes[i]; 1079 | 1080 | if (tempNode.Output != null && tempNode.Output.Window.Contains(position)) 1081 | { 1082 | return tempNode.Output; 1083 | } 1084 | } 1085 | 1086 | return null; 1087 | } 1088 | 1089 | /// 1090 | /// Convert a global graph position to the local GUI position 1091 | /// 1092 | /// The graph position before panning is applied 1093 | /// The local GUI position after panning is applied 1094 | public Vector2 ConvertToLocalPosition(Vector2 inputPosition) 1095 | { 1096 | inputPosition.x += this.panX; 1097 | inputPosition.y += this.panY; 1098 | return inputPosition; 1099 | } 1100 | 1101 | /// 1102 | /// Convert a local GUI position to the global position on the graph 1103 | /// 1104 | /// The local GUI position after panning is applied 1105 | /// The graph position before panning is applied 1106 | public Vector2 ConvertToGlobalPosition(Vector2 inputPosition) 1107 | { 1108 | inputPosition.x -= this.panX; 1109 | inputPosition.y -= this.panY; 1110 | return inputPosition; 1111 | } 1112 | 1113 | #endregion 1114 | } 1115 | } -------------------------------------------------------------------------------- /Editor/AudioGraph.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e674289f655473e4aa0c5ac850c31b5e 3 | timeCreated: 1518722167 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Editor/AudioProfiler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | using UnityEngine.Audio; 7 | using System.Collections.Generic; 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | /// 12 | /// Display for visualizing the currently-playing AudioEvents when the experience is running 13 | /// 14 | public class AudioProfiler : EditorWindow 15 | { 16 | /// 17 | /// Collection of currently playing events for the number of past saved frames 18 | /// 19 | private List profilerFrames = new List(); 20 | /// 21 | /// The frame currently being viewed in the window 22 | /// 23 | private int currentFrame = 0; 24 | /// 25 | /// The vertical position of the next event to be displayed in the window 26 | /// 27 | private float eventY = 0; 28 | /// 29 | /// The vertical position of the next emitter to be displayed in the window 30 | /// 31 | private float emitterY = 0; 32 | /// 33 | /// The vertical position of the next bus to be displayed in the window 34 | /// 35 | private float busY = 0; 36 | /// 37 | /// The horizontal position of the next event to be displayed in the window 38 | /// 39 | private const float eventX = 220; 40 | /// 41 | /// The horizontal position of the next emitter to be displayed in the window 42 | /// 43 | private const float emitterX = eventX + 220; 44 | /// 45 | /// The horizontal position of the next bus to be displayed in the window 46 | /// 47 | private const float busX = emitterX + 220; 48 | /// 49 | /// The height of the GUI window for all nodes 50 | /// 51 | private const float WindowHeight = 100; 52 | /// 53 | /// The width of the GUI window for all nodes 54 | /// 55 | private const float WindowWidth = 200; 56 | /// 57 | /// The amount of vertical space between nodes 58 | /// 59 | private const float WindowYInterval = 120; 60 | /// 61 | /// Window for listing previous events 62 | /// 63 | private Rect eventListRect = new Rect(0, 20, 300, 400); 64 | /// 65 | /// The scroll position of the event list 66 | /// 67 | private Vector2 eventListScrollPosition = new Vector2(); 68 | /// 69 | /// The maximum number of saved previous frames in the profiler 70 | /// 71 | private const int MaxFrames = 300; 72 | 73 | /// 74 | /// Display the profiler window 75 | /// 76 | [MenuItem("Window/Audio Profiler")] 77 | private static void OpenAudioProfiler() 78 | { 79 | AudioProfiler profiler = GetWindow(); 80 | profiler.Show(); 81 | } 82 | 83 | private void Update() 84 | { 85 | if (EditorApplication.isPlaying && !EditorApplication.isPaused) 86 | { 87 | CollectProfilerEvents(); 88 | } 89 | 90 | Repaint(); 91 | } 92 | 93 | private void OnGUI() 94 | { 95 | DrawEventList(); 96 | 97 | if (this.profilerFrames.Count > 0) 98 | { 99 | this.currentFrame = EditorGUILayout.IntSlider(this.currentFrame, 0, this.profilerFrames.Count - 1); 100 | } 101 | else 102 | { 103 | return; 104 | } 105 | 106 | if (this.profilerFrames.Count > this.currentFrame) 107 | { 108 | DrawProfilerFrame(this.profilerFrames[this.currentFrame]); 109 | } 110 | 111 | if (EditorApplication.isPlaying && !EditorApplication.isPaused) 112 | { 113 | this.currentFrame = this.profilerFrames.Count - 1; 114 | } 115 | } 116 | 117 | /// 118 | /// Get data for all ActiveEvents in the AudioManager 119 | /// 120 | private void CollectProfilerEvents() 121 | { 122 | List activeEvents = AudioManager.ActiveEvents; 123 | ProfilerEvent[] currentEvents = new ProfilerEvent[activeEvents.Count]; 124 | for (int i = 0; i < currentEvents.Length; i++) 125 | { 126 | ActiveEvent tempActiveEvent = activeEvents[i]; 127 | if (tempActiveEvent.sources.Count == 0) 128 | { 129 | //Debug.LogFormat("No sources for event {0}", tempActiveEvent.name); 130 | //continue; 131 | } 132 | ProfilerEvent tempProfilerEvent = new ProfilerEvent(); 133 | tempProfilerEvent.eventName = tempActiveEvent.name; 134 | if (tempActiveEvent.sources != null && tempActiveEvent.sources.Count > 0) 135 | { 136 | tempProfilerEvent.clip = tempActiveEvent.sources[0].source.clip; 137 | tempProfilerEvent.emitterObject = tempActiveEvent.sources[0].source.gameObject; 138 | tempProfilerEvent.bus = tempActiveEvent.rootEvent.Output.mixerGroup; 139 | } 140 | tempProfilerEvent.activeEvent = tempActiveEvent; 141 | currentEvents[i] = tempProfilerEvent; 142 | } 143 | this.profilerFrames.Add(currentEvents); 144 | 145 | while(this.profilerFrames.Count > MaxFrames) 146 | { 147 | this.profilerFrames.RemoveAt(0); 148 | } 149 | } 150 | 151 | private void DrawEventList() 152 | { 153 | this.eventListRect.height = this.position.height; 154 | GUILayout.BeginArea(this.eventListRect); 155 | this.eventListScrollPosition = EditorGUILayout.BeginScrollView(this.eventListScrollPosition); 156 | 157 | for (int i = 0; i < AudioManager.PreviousEvents.Count; i++) 158 | { 159 | ActiveEvent tempEvent = AudioManager.PreviousEvents[i]; 160 | GUILayout.Label(tempEvent.timeStarted + " : " + tempEvent.name + " - " + tempEvent.status.ToString()); 161 | } 162 | 163 | EditorGUILayout.EndScrollView(); 164 | GUILayout.EndArea(); 165 | } 166 | 167 | /// 168 | /// Draw the nodes for the specified frame 169 | /// 170 | /// The frame to show the ActiveEvents for 171 | private void DrawProfilerFrame(ProfilerEvent[] profilerEvents) 172 | { 173 | this.eventY = 20; 174 | this.emitterY = 20; 175 | this.busY = 20; 176 | List buses = new List(); 177 | List emitters = new List(); 178 | 179 | BeginWindows(); 180 | for (int i = 0; i < profilerEvents.Length; i++) 181 | { 182 | bool addedEmitter = false; 183 | ProfilerEvent tempEvent = profilerEvents[i]; 184 | //this.currentProfiledEvent = tempEvent; 185 | if (tempEvent == null) 186 | { 187 | continue; 188 | } 189 | 190 | //GUI.Window(i, new Rect(eventX, this.eventY, WindowWidth, WindowHeight), DrawWindow, tempEvent.eventName); 191 | tempEvent.activeEvent.DrawNode(i, new Rect(eventX, this.eventY, WindowWidth, WindowHeight)); 192 | if (!emitters.Contains(tempEvent.emitterObject)) 193 | { 194 | emitters.Add(tempEvent.emitterObject); 195 | string emitterName = tempEvent.emitterObject == null ? "No emitter" : tempEvent.emitterObject.name; 196 | GUI.Window(i + 200, new Rect(emitterX, this.emitterY, WindowWidth, WindowHeight), DrawWindow, emitterName); 197 | DrawCurve(new Vector2(eventX + WindowWidth, this.eventY), new Vector2(emitterX, this.emitterY)); 198 | addedEmitter = true; 199 | } 200 | else 201 | { 202 | //Draw a line to this emitter 203 | int emitterNum = emitters.IndexOf(tempEvent.emitterObject); 204 | DrawCurve(new Vector2(eventX + WindowWidth, this.eventY), new Vector2(emitterX, 20 + WindowYInterval * emitterNum)); 205 | } 206 | 207 | if (!buses.Contains(tempEvent.bus)) 208 | { 209 | buses.Add(tempEvent.bus); 210 | if (tempEvent.bus == null) 211 | { 212 | GUI.Window(i + 100, new Rect(busX, this.busY, WindowWidth, WindowHeight), DrawWindow, "-No Bus-"); 213 | } 214 | else 215 | { 216 | GUI.Window(i + 100, new Rect(busX, this.busY, WindowWidth, WindowHeight), DrawWindow, tempEvent.bus.name); 217 | } 218 | DrawCurve(new Vector2(emitterX + WindowWidth, this.emitterY), new Vector2(busX, this.busY)); 219 | this.busY += WindowYInterval; 220 | } 221 | else 222 | { 223 | //Draw a line to this bus 224 | int busNum = buses.IndexOf(tempEvent.bus); 225 | DrawCurve(new Vector2(emitterX + WindowWidth, this.emitterY), new Vector2(busX, 20 + WindowYInterval * busNum)); 226 | } 227 | 228 | this.eventY += WindowYInterval; 229 | if (addedEmitter) 230 | { 231 | this.emitterY += WindowYInterval; 232 | } 233 | } 234 | EndWindows(); 235 | } 236 | 237 | /// 238 | /// Draw the Unity DragWindow 239 | /// 240 | /// Index of the window to draw 241 | private void DrawWindow(int id) 242 | { 243 | //EditorGUILayout.TextField("Volume:" + this.currentProfiledEvent.activeEvent.source.volume.ToString()); 244 | GUI.DragWindow(); 245 | } 246 | 247 | /// 248 | /// Draw a line between two points using a Bezier curve 249 | /// 250 | /// Initial position of the line 251 | /// Final position of the line 252 | public static void DrawCurve(Vector2 start, Vector2 end) 253 | { 254 | Vector3 startPosition = new Vector3(start.x, start.y); 255 | Vector3 endPosition = new Vector3(end.x, end.y); 256 | Vector3 startTangent = startPosition + (Vector3.right * 50); 257 | Vector3 endTangent = endPosition + (Vector3.left * 50); 258 | Handles.DrawBezier(startPosition, endPosition, startTangent, endTangent, Color.white, null, 2); 259 | } 260 | } 261 | } -------------------------------------------------------------------------------- /Editor/AudioProfiler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 816211ec7df0b654ea06c10a101c04e8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProfilerEvent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | using UnityEngine.Audio; 6 | 7 | namespace Microsoft.MixedReality.Toolkit.Audio 8 | { 9 | /// 10 | /// A minimal version of an ActiveEvent only containing what is needed for the profiler 11 | /// 12 | public class ProfilerEvent 13 | { 14 | /// 15 | /// The name of the ActiveEvent being profiled 16 | /// 17 | public string eventName = ""; 18 | /// 19 | /// The name of the audio file being played in the event 20 | /// 21 | public AudioClip clip; 22 | /// 23 | /// The GameObject containing the AudioSource component playing the event 24 | /// 25 | public GameObject emitterObject; 26 | /// 27 | /// The audio bus the event is routed to 28 | /// 29 | public AudioMixerGroup bus; 30 | /// 31 | /// The Active Event reference for the playing sound 32 | /// 33 | public ActiveEvent activeEvent; 34 | } 35 | } -------------------------------------------------------------------------------- /Editor/ProfilerEvent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d42b390c1c58fef409150eaf76fa4861 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/com.microsoft.audiomanagerforunity.editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.microsoft.edt.lib.audiomanager.editor", 3 | "references": [ 4 | "GUID:60c6551998711ca4b9d346347b638d74" 5 | ], 6 | "includePlatforms": [ 7 | "Editor" 8 | ], 9 | "excludePlatforms": [], 10 | "allowUnsafeCode": false, 11 | "overrideReferences": false, 12 | "precompiledReferences": [], 13 | "autoReferenced": true, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /Editor/com.microsoft.audiomanagerforunity.editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eab1b4e90a4752a45bc67ff4328c748a 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing 3 | 4 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 5 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 6 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 7 | 8 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 9 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 10 | provided by the bot. You will only need to do this once across all repos using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 14 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a51bb11551bdf846be32eae43f98986 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/ActiveEvent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using UnityEngine; 7 | using System; 8 | #if UNITY_EDITOR 9 | using UnityEditor; 10 | #endif 11 | 12 | namespace Microsoft.MixedReality.Toolkit.Audio 13 | { 14 | /// 15 | /// The runtime event of an Audio Event that is currently playing 16 | /// 17 | [System.Serializable] 18 | public class ActiveEvent 19 | { 20 | /// 21 | /// The name of the audio event to be played 22 | /// 23 | public string name = ""; 24 | /// 25 | /// The AudioEvent that is being played 26 | /// 27 | public AudioEvent rootEvent { get; private set; } 28 | /// 29 | /// The AudioSource to play the AudioEvent on 30 | /// 31 | public List sources { get; private set; } = new List(); 32 | /// 33 | /// The latest status of the event 34 | /// 35 | public EventStatus status = EventStatus.Initialized; 36 | /// 37 | /// The AudioParameters in use by the event 38 | /// 39 | private ActiveParameter[] activeParameters; 40 | public string timeStarted = ""; 41 | /// 42 | /// The Transform to use to calculate the user's gaze position 43 | /// 44 | private Transform gazeReference; 45 | /// 46 | /// The transform that the sound should follow in the scene 47 | /// 48 | internal Transform emitterTransform = null; 49 | /// 50 | /// The text associated with the event, usually for subtitles 51 | /// 52 | internal string text = ""; 53 | /// 54 | /// Time in seconds before the audio file will start playing 55 | /// 56 | internal float initialDelay = 0; 57 | /// 58 | /// The volume of the event before parameters are applied 59 | /// 60 | private float eventVolume = 0; 61 | /// 62 | /// The volume that the event is fading to or settled on after fading 63 | /// 64 | private float targetVolume = 1; 65 | /// 66 | /// The pitch value 67 | /// 68 | private float eventPitch = 1; 69 | /// 70 | /// The amount of time in seconds that the event will fade in or out 71 | /// 72 | private float targetFadeTime = 0; 73 | /// 74 | /// The amount of time in seconds that the event has been fading in or out 75 | /// 76 | private float currentFadeTime = 0; 77 | /// 78 | /// The amount of time in seconds since the event started 79 | /// 80 | private float elapsedTime = 0; 81 | /// 82 | /// The previous volume the event was at before starting a fade 83 | /// 84 | private float fadeOriginVolume = 0; 85 | /// 86 | /// Whether the event is currently fading out to be stopped 87 | /// 88 | private bool fadeStopQueued = false; 89 | /// 90 | /// Whether the event has played a sound yet 91 | /// 92 | private bool hasPlayed = false; 93 | /// 94 | /// Whether the event is using the user's gaze for a parameter 95 | /// 96 | private bool useGaze = false; 97 | private bool callbackActivated = false; 98 | private bool hasTimeAdvanced = false; 99 | internal bool hasSnapshotTransition = false; 100 | 101 | /// 102 | /// The time left on the event unless it is stopped or the pitch changes 103 | /// 104 | public float EstimatedRemainingTime { get; private set; } 105 | /// 106 | /// Whether the event has been muted 107 | /// 108 | public bool Muted { get; private set; } = false; 109 | /// 110 | /// Whether the event has been soloed 111 | /// 112 | public bool Soloed { get; private set; } = false; 113 | 114 | /// 115 | /// Delegate for event completion callback 116 | /// 117 | public delegate void EventCompleted(); 118 | 119 | /// 120 | /// Callback when the event stops 121 | /// 122 | public event EventCompleted CompletionCallback; 123 | 124 | /// 125 | /// Constructor: Create a new ActiveEvent from an AudioEvent and play it on an AudioSource 126 | /// 127 | /// The AudioEvent to play 128 | /// The AudioSource use for the AudioEvent 129 | public ActiveEvent(AudioEvent eventToPlay, Transform emitterTransform) 130 | { 131 | this.rootEvent = eventToPlay; 132 | this.name = eventToPlay.name; 133 | this.emitterTransform = emitterTransform; 134 | 135 | InitializeParameters(); 136 | } 137 | 138 | /// 139 | /// Internal AudioManager use: starts the audio event 140 | /// 141 | public void Play() 142 | { 143 | this.timeStarted = Time.time.ToString(); 144 | this.rootEvent.SetActiveEventProperties(this); 145 | 146 | if (this.sources.Count == 0 && !this.hasSnapshotTransition) 147 | { 148 | this.status = EventStatus.Error; 149 | AudioManager.AddPreviousEvent(this); 150 | return; 151 | } 152 | 153 | SetStartingSourceProperties(); 154 | 155 | this.status = EventStatus.Played; 156 | 157 | AudioManager.ActiveEvents.Add(this); 158 | AudioManager.AddPreviousEvent(this); 159 | } 160 | 161 | /// 162 | /// Internal AudioManager use: update fade and RTPC values 163 | /// 164 | public void Update() 165 | { 166 | if (this.rootEvent == null) 167 | { 168 | Debug.LogWarningFormat(this.emitterTransform, "AudioManager: Can't find audio event for {0}!", this.name); 169 | StopImmediate(); 170 | return; 171 | } 172 | 173 | this.elapsedTime += Time.deltaTime; 174 | 175 | if (!this.hasPlayed && this.elapsedTime >= this.initialDelay) 176 | { 177 | this.hasPlayed = true; 178 | PlayAllSources(); 179 | } 180 | 181 | if (this.emitterTransform != null) 182 | { 183 | SetAllSourcePositions(this.emitterTransform.position); 184 | } 185 | 186 | if (this.hasPlayed && this.currentFadeTime < this.targetFadeTime) 187 | { 188 | UpdateFade(); 189 | } 190 | 191 | if (!this.rootEvent.Output.loop) 192 | { 193 | UpdateRemainingTime(); 194 | if (this.hasPlayed && !IsAnySourcePlaying()) 195 | { 196 | StopImmediate(); 197 | } 198 | } 199 | 200 | if (this.useGaze) 201 | { 202 | UpdateGaze(); 203 | } 204 | 205 | UpdateParameters(); 206 | ApplyParameters(); 207 | } 208 | 209 | /// 210 | /// Stops the event using the default fade time if applicable 211 | /// 212 | public void Stop() 213 | { 214 | if (this.rootEvent.FadeOut <= 0) 215 | { 216 | StopImmediate(); 217 | } 218 | else 219 | { 220 | this.targetVolume = 0; 221 | this.fadeOriginVolume = this.sources[0].source.volume; 222 | this.targetFadeTime = this.rootEvent.FadeOut; 223 | this.currentFadeTime = 0; 224 | this.fadeStopQueued = true; 225 | } 226 | } 227 | 228 | /// 229 | /// Stops the event immediately, ignoring the event's fade time 230 | /// 231 | public void StopImmediate() 232 | { 233 | if (this.CompletionCallback != null && !this.callbackActivated) 234 | { 235 | this.callbackActivated = true; 236 | CompletionCallback(); 237 | } 238 | 239 | this.status = EventStatus.Stopped; 240 | 241 | StopAllSources(); 242 | 243 | AudioManager.DelayRemoveActiveEvent(this); 244 | } 245 | 246 | /// 247 | /// Set the value of a parameter on this event independent of the root parameter's value 248 | /// 249 | /// The AudioParameter set on the root AudioEvent 250 | /// The new local value on this event instance (does not set the AudioParameter value) 251 | public void SetLocalParameter(AudioParameter localParameter, float newValue) 252 | { 253 | bool hasParameter = false; 254 | for (int i = 0; i < this.activeParameters.Length; i++) 255 | { 256 | ActiveParameter tempParameter = this.activeParameters[i]; 257 | if (tempParameter.rootParameter.parameter == localParameter) 258 | { 259 | hasParameter = true; 260 | tempParameter.CurrentValue = newValue; 261 | } 262 | } 263 | 264 | if (!hasParameter) 265 | { 266 | //Debug.LogWarningFormat("Audio event {0} does not have parameter {1}", this.rootEvent.name, localParameter.name); 267 | } 268 | } 269 | 270 | public bool AddEventSource(AudioClip clip, AudioParameter parameter = null, AnimationCurve responseCurve = null, float startTime = 0) 271 | { 272 | if (this.rootEvent == null) 273 | { 274 | Debug.LogWarningFormat(this.emitterTransform, "AudioManager: Can't find audio event for {0}!", this.name); 275 | StopImmediate(); 276 | return false; 277 | } 278 | 279 | EventSource newSource = new EventSource(); 280 | newSource.source = AudioManager.GetUnusedSource(); 281 | 282 | if (newSource.source == null) 283 | { 284 | Debug.LogWarningFormat(this.emitterTransform, "AudioManager: Can't find unused audio source for event {0}!", this.name); 285 | StopImmediate(); 286 | return false; 287 | } 288 | 289 | newSource.source.loop = this.rootEvent.Output.loop; 290 | newSource.source.clip = clip; 291 | newSource.parameter = parameter; 292 | newSource.responseCurve = responseCurve; 293 | newSource.startTime = startTime; 294 | sources.Add(newSource); 295 | 296 | return true; 297 | } 298 | 299 | /// 300 | /// Internal AudioManager use: initializes the volume based on the event's AudioOutput 301 | /// 302 | /// The target volume for the AudioSource (not necessarily the current volume) 303 | public void SetVolume(float newVolume) 304 | { 305 | this.targetVolume = newVolume; 306 | } 307 | 308 | /// 309 | /// Offset the volume by the specified amount 310 | /// 311 | /// A number between -1 and 1 for volume changes 312 | public void ModulateVolume(float volumeDelta) 313 | { 314 | this.targetVolume += volumeDelta; 315 | } 316 | 317 | /// 318 | /// Overwrite the pitch with a new value 319 | /// 320 | /// Pitch value between -1 and 3 321 | public void SetPitch(float newPitch) 322 | { 323 | if (newPitch <= 0) 324 | { 325 | Debug.LogWarningFormat("Invalid pitch set in event {0}", this.rootEvent.name); 326 | return; 327 | } 328 | 329 | this.eventPitch = newPitch; 330 | } 331 | 332 | /// 333 | /// Offset the pitch by the specified amount 334 | /// 335 | /// A number between -1 and 3 for pitch changes 336 | public void ModulatePitch(float pitchDelta) 337 | { 338 | this.eventPitch += pitchDelta; 339 | } 340 | 341 | public void SetEmitterPosition(Vector3 newPos) 342 | { 343 | if (this.sources.Count > 0) 344 | { 345 | this.sources[0].source.transform.position = newPos; 346 | } 347 | else 348 | { 349 | Debug.LogWarningFormat(this.emitterTransform, "No audio emitter for active event {0}", this.name); 350 | } 351 | } 352 | 353 | #region Source Property Settings 354 | 355 | private void SetStartingSourceProperties() 356 | { 357 | if (this.rootEvent.FadeIn > 0) 358 | { 359 | SetallSourceVolumes(0); 360 | this.eventVolume = 0; 361 | this.fadeOriginVolume = 0; 362 | this.currentFadeTime = 0; 363 | this.targetFadeTime = this.rootEvent.FadeIn; 364 | } 365 | else 366 | { 367 | SetallSourceVolumes(this.targetVolume); 368 | this.eventVolume = this.targetVolume; 369 | } 370 | 371 | SetAllSourcePitches(this.eventPitch); 372 | 373 | this.useGaze = HasGazeProperty(); 374 | if (this.useGaze) 375 | { 376 | this.gazeReference = Camera.main.transform; 377 | UpdateGaze(); 378 | } 379 | 380 | ApplyParameters(); 381 | 382 | if (this.initialDelay == 0) 383 | { 384 | this.hasPlayed = true; 385 | PlayAllSources(); 386 | } 387 | } 388 | 389 | private void SetallSourceVolumes(float newVolume) 390 | { 391 | for (int i = 0; i < this.sources.Count; i++) 392 | { 393 | this.sources[i].source.volume = newVolume; 394 | } 395 | } 396 | 397 | private void SetAllSourcePitches(float newPitch) 398 | { 399 | for (int i = 0; i < this.sources.Count; i++) 400 | { 401 | this.sources[i].source.pitch = newPitch; 402 | } 403 | } 404 | 405 | private void PlayAllSources() 406 | { 407 | for (int i = 0; i < this.sources.Count; i++) 408 | { 409 | this.sources[i].source.Play(); 410 | this.sources[i].source.time = this.sources[i].startTime; 411 | } 412 | } 413 | 414 | private void StopAllSources() 415 | { 416 | for (int i = 0; i < this.sources.Count; i++) 417 | { 418 | EventSource tempSource = this.sources[i]; 419 | if (tempSource != null && tempSource.source != null) 420 | { 421 | tempSource.source.Stop(); 422 | } 423 | } 424 | } 425 | 426 | public void SetAllSourcePositions(Vector3 position) 427 | { 428 | for (int i = 0; i < this.sources.Count; i++) 429 | { 430 | this.sources[i].source.transform.position = position; 431 | } 432 | } 433 | 434 | private bool IsAnySourcePlaying() 435 | { 436 | for (int i = 0; i < this.sources.Count; i++) 437 | { 438 | if (this.sources[i].source.isPlaying) 439 | { 440 | return true; 441 | } 442 | } 443 | 444 | return false; 445 | } 446 | 447 | #endregion 448 | 449 | #region Private Functions 450 | 451 | private void InitializeParameters() 452 | { 453 | this.activeParameters = new ActiveParameter[this.rootEvent.Parameters.Count]; 454 | for (int i = 0; i < this.activeParameters.Length; i++) 455 | { 456 | if (this.rootEvent.Parameters[i] != null) 457 | { 458 | this.activeParameters[i] = new ActiveParameter(this.rootEvent.Parameters[i]); 459 | } 460 | } 461 | } 462 | 463 | /// 464 | /// Internal AudioManager use: Sync changes to the parameters on all ActiveEvents 465 | /// 466 | private void UpdateParameters() 467 | { 468 | for (int i = 0; i < this.activeParameters.Length; i++) 469 | { 470 | AudioEventParameter tempParam = this.activeParameters[i].rootParameter; 471 | if (tempParam == null || tempParam.parameter == null) 472 | { 473 | continue; 474 | } 475 | 476 | if (tempParam.CurrentValue != tempParam.parameter.CurrentValue) 477 | { 478 | tempParam.SyncParameter(); 479 | } 480 | } 481 | } 482 | 483 | /// 484 | /// Internal AudioManager use: applies parameter changes 485 | /// 486 | private void ApplyParameters() 487 | { 488 | float tempVolume = this.eventVolume; 489 | float tempPitch = this.eventPitch; 490 | for (int i = 0; i < this.activeParameters.Length; i++) 491 | { 492 | ActiveParameter tempParameter = this.activeParameters[i]; 493 | switch (tempParameter.rootParameter.paramType) 494 | { 495 | case ParameterType.Volume: 496 | tempVolume *= tempParameter.CurrentResult; 497 | break; 498 | case ParameterType.Pitch: 499 | tempPitch *= tempParameter.CurrentResult; 500 | break; 501 | } 502 | } 503 | 504 | for (int i = 0; i < this.sources.Count; i++) 505 | { 506 | EventSource tempSource = this.sources[i]; 507 | tempSource.source.volume = tempVolume; 508 | tempSource.source.pitch = tempPitch; 509 | if (tempSource.parameter != null) 510 | { 511 | float volumeScale = tempSource.responseCurve.Evaluate(tempSource.parameter.CurrentValue); 512 | tempSource.source.volume = this.eventVolume * volumeScale; 513 | } 514 | } 515 | } 516 | 517 | /// 518 | /// Internal AudioManager use: update volume on ActiveEvents fading in and out 519 | /// 520 | private void UpdateFade() 521 | { 522 | float percentageFaded = (this.currentFadeTime / this.targetFadeTime); 523 | 524 | if (this.targetVolume > this.fadeOriginVolume) 525 | { 526 | this.eventVolume = this.fadeOriginVolume + ((this.targetVolume - this.fadeOriginVolume) * percentageFaded); 527 | } 528 | else 529 | { 530 | this.eventVolume = this.fadeOriginVolume - ((this.fadeOriginVolume - this.targetVolume) * percentageFaded); 531 | } 532 | 533 | this.currentFadeTime += Time.deltaTime; 534 | 535 | if (this.currentFadeTime >= this.targetFadeTime) 536 | { 537 | this.eventVolume = this.targetVolume; 538 | 539 | if (this.fadeStopQueued) 540 | { 541 | StopImmediate(); 542 | } 543 | } 544 | 545 | SetallSourceVolumes(this.eventVolume); 546 | } 547 | 548 | /// 549 | /// Recalculate estimated time the event will be active for 550 | /// 551 | private void UpdateRemainingTime() 552 | { 553 | if (this.rootEvent == null) 554 | { 555 | Debug.LogWarningFormat(this.emitterTransform, "AudioManager: Can't find audio event for {0}!", this.name); 556 | StopImmediate(); 557 | return; 558 | } 559 | 560 | AudioSource mainSource = this.sources[0].source; 561 | if (mainSource == null || mainSource.clip == null || mainSource.clip.length <= 0) 562 | { 563 | StopImmediate(); 564 | return; 565 | } 566 | 567 | if (this.rootEvent.Output.loop) 568 | { 569 | this.EstimatedRemainingTime = 500; 570 | } 571 | else 572 | { 573 | this.EstimatedRemainingTime = (mainSource.clip.length - mainSource.time) / mainSource.pitch; 574 | } 575 | 576 | if (this.hasTimeAdvanced && mainSource.time == 0) 577 | { 578 | StopImmediate(); 579 | } 580 | 581 | if (this.EstimatedRemainingTime <= this.rootEvent.FadeOut) 582 | { 583 | Stop(); 584 | } 585 | } 586 | 587 | /// 588 | /// Check if any of the parameters on the event use gaze for the value 589 | /// 590 | /// Whether at least one parameter uses gaze 591 | private bool HasGazeProperty() 592 | { 593 | for (int i = 0; i < this.rootEvent.Parameters.Count; i++) 594 | { 595 | AudioParameter tempParam = this.rootEvent.Parameters[i].parameter; 596 | 597 | if (tempParam == null) 598 | { 599 | Debug.LogWarningFormat("Audio event '{0}' has a null parameter!", this.rootEvent.name); 600 | continue; 601 | } 602 | 603 | if (tempParam.UseGaze) 604 | { 605 | return true; 606 | } 607 | } 608 | 609 | return false; 610 | } 611 | 612 | /// 613 | /// Get the user's head position relative to the audio event, and set the property's value 614 | /// 615 | private void UpdateGaze() 616 | { 617 | AudioSource mainSource = this.sources[0].source; 618 | float gazeAngle = 0; 619 | if (this.gazeReference == null) 620 | { 621 | this.gazeReference = Camera.main.transform; 622 | } 623 | 624 | Vector3 posDelta = this.gazeReference.position - mainSource.transform.position; 625 | gazeAngle = Mathf.Abs(180 - Vector3.Angle(this.gazeReference.forward, posDelta)); 626 | 627 | for (int i = 0; i < this.activeParameters.Length; i++) 628 | { 629 | ActiveParameter tempParameter = this.activeParameters[i]; 630 | if (tempParameter.rootParameter.parameter.UseGaze) 631 | { 632 | tempParameter.CurrentValue = gazeAngle; 633 | } 634 | } 635 | } 636 | 637 | #endregion 638 | 639 | #region Editor 640 | 641 | /// 642 | /// Set whether the defined parameter on the event uses the player's gaze angle 643 | /// 644 | /// 645 | /// 646 | public void ToggleGaze(bool toggle, AudioParameter gazeParameter) 647 | { 648 | this.useGaze = toggle; 649 | 650 | if (!toggle) 651 | { 652 | for (int i = 0; i < this.activeParameters.Length; i++) 653 | { 654 | ActiveParameter tempParameter = this.activeParameters[i]; 655 | if (tempParameter.rootParameter.parameter == gazeParameter) 656 | { 657 | tempParameter.Reset(); 658 | } 659 | } 660 | } 661 | } 662 | 663 | /// 664 | /// Toggles whether the event is audible 665 | /// 666 | public void ToggleMute() 667 | { 668 | SetMute(!this.Muted); 669 | } 670 | 671 | /// 672 | /// Sets whether the event is audible 673 | /// 674 | /// Whether sound should be made inaudible 675 | public void SetMute(bool toggle) 676 | { 677 | this.Muted = toggle; 678 | for (int i = 0; i < this.sources.Count; i++) 679 | { 680 | this.sources[i].source.mute = toggle; 681 | } 682 | } 683 | 684 | /// 685 | /// Toggles whether only this sound (and other soloed sounds) are audible 686 | /// 687 | public void ToggleSolo() 688 | { 689 | this.Soloed = !this.Soloed; 690 | AudioManager.ApplyActiveSolos(); 691 | } 692 | 693 | /// 694 | /// Mutes all other non-soloed events 695 | /// 696 | /// Whether this event is part of the isolated audible events 697 | public void SetSolo(bool toggle) 698 | { 699 | this.Soloed = toggle; 700 | AudioManager.ApplyActiveSolos(); 701 | } 702 | 703 | /// 704 | /// Internal AudioManager use: applies solo property to AudioSource, ignoring "mute" property 705 | /// 706 | public void ApplySolo() 707 | { 708 | SetMute(!this.Soloed); 709 | } 710 | 711 | /// 712 | /// Internal AudioManager use: clears all solo properties, and reverts to mute property 713 | /// 714 | public void ClearSolo() 715 | { 716 | this.Soloed = false; 717 | SetMute(this.Muted); 718 | } 719 | 720 | #if UNITY_EDITOR 721 | 722 | public void DisplayProperties() 723 | { 724 | if (this.sources.Count == 0 || this.sources[0] == null || this.sources[0].source == null) 725 | { 726 | return; 727 | } 728 | 729 | if (emitterTransform != null) 730 | { 731 | EditorGUILayout.LabelField("Emitter:" + emitterTransform.gameObject.name); 732 | } 733 | else 734 | { 735 | EditorGUILayout.LabelField("Emitter:" + this.sources[0].source.gameObject.name); 736 | } 737 | } 738 | 739 | protected void DrawWindow(int id) 740 | { 741 | GUI.DragWindow(); 742 | DisplayProperties(); 743 | } 744 | 745 | public virtual void DrawNode(int id, Rect window) 746 | { 747 | GUI.Window(id, window, DrawWindow, this.name); 748 | } 749 | 750 | #endif 751 | 752 | #endregion 753 | } 754 | public class EventSource 755 | { 756 | public AudioSource source = new AudioSource(); 757 | public AudioParameter parameter = null; 758 | public AnimationCurve responseCurve = null; 759 | public float startTime = 0; 760 | } 761 | 762 | public enum EventStatus 763 | { 764 | Initialized, 765 | Played, 766 | Stopped, 767 | Error 768 | } 769 | } 770 | -------------------------------------------------------------------------------- /Runtime/ActiveEvent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3b37b4b6302b40740b6cf28bb5e7e9c3 3 | timeCreated: 1519170352 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/ActiveParameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace Microsoft.MixedReality.Toolkit.Audio 5 | { 6 | /// 7 | /// The runtime instance of an AudioParameter on an ActiveEvent 8 | /// 9 | public class ActiveParameter 10 | { 11 | /// 12 | /// The value of the parameter 13 | /// 14 | private float currentValue = 0; 15 | /// 16 | /// The result of the parameter's curve on the currentValue 17 | /// 18 | private float currentResult = 0; 19 | /// 20 | /// Has the ActiveParameter been set to a value independent from the main AudioParameter 21 | /// 22 | private bool isDirty = false; 23 | 24 | /// 25 | /// Constructor: Create a new ActiveParameter from the EventParameter 26 | /// 27 | /// The EventParameter to apply to this event 28 | public ActiveParameter(AudioEventParameter root) 29 | { 30 | this.rootParameter = root; 31 | } 32 | 33 | /// 34 | /// The EventParameter being used 35 | /// 36 | public AudioEventParameter rootParameter { get; private set; } 37 | /// 38 | /// The value of the root parameter, unless the ActiveParameter has been independently set 39 | /// 40 | public float CurrentValue 41 | { 42 | get 43 | { 44 | if (this.isDirty) 45 | { 46 | return this.currentValue; 47 | } 48 | else 49 | { 50 | return this.rootParameter.parameter.CurrentValue; 51 | } 52 | } 53 | set 54 | { 55 | this.currentValue = value; 56 | this.currentResult = this.rootParameter.ProcessParameter(this.currentValue); 57 | this.isDirty = true; 58 | } 59 | } 60 | 61 | /// 62 | /// The result of the current value applied to the response curve 63 | /// 64 | public float CurrentResult 65 | { 66 | get { 67 | if (this.isDirty) 68 | { 69 | return this.currentResult; 70 | } 71 | else 72 | { 73 | return this.rootParameter.CurrentResult; 74 | } 75 | } 76 | } 77 | 78 | /// 79 | /// Clear the modified value and use the global parameter's value 80 | /// 81 | public void Reset() 82 | { 83 | this.currentValue = this.rootParameter.parameter.CurrentValue; 84 | this.currentResult = this.rootParameter.CurrentResult; 85 | this.isDirty = false; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Runtime/ActiveParameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 413b58b9b05feb341b862d1b81b979da 3 | timeCreated: 1523842945 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioBank.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | using System.Collections.Generic; 6 | #if UNITY_EDITOR 7 | using UnityEditor; 8 | #endif 9 | 10 | namespace Microsoft.MixedReality.Toolkit.Audio 11 | { 12 | /// 13 | /// A collection of AudioEvents and AudioParameters 14 | /// 15 | [CreateAssetMenu(menuName = "Mixed Reality Toolkit/Audio Bank")] 16 | public class AudioBank : ScriptableObject 17 | { 18 | /// 19 | /// The events included in the bank 20 | /// 21 | [SerializeField] 22 | private List audioEvents; 23 | /// 24 | /// The parameters included in the bank 25 | /// 26 | [SerializeField] 27 | private List parameters = new List(); 28 | 29 | [SerializeField] 30 | private List switches = new List(); 31 | 32 | /// 33 | /// The public accessor for the events in the bank 34 | /// 35 | public List AudioEvents 36 | { 37 | get { return this.audioEvents; } 38 | } 39 | 40 | private void OnEnable() 41 | { 42 | if (this.audioEvents == null) 43 | { 44 | this.audioEvents = new List(); 45 | } 46 | } 47 | 48 | #if UNITY_EDITOR 49 | 50 | /// 51 | /// EDITOR: Get or set the array of AudioEvents 52 | /// 53 | public List EditorEvents 54 | { 55 | get { return this.audioEvents; } 56 | set { this.audioEvents = value; } 57 | } 58 | 59 | /// 60 | /// EDITOR: Get or set the array of AudioParameters 61 | /// 62 | public List EditorParameters 63 | { 64 | get { return this.parameters; } 65 | } 66 | 67 | public List EditorSwitches 68 | { 69 | get { return this.switches; } 70 | } 71 | 72 | /// 73 | /// EDITOR: Create a new event and add it to the array of events in the bank 74 | /// 75 | /// The position of the Output node 76 | /// 77 | public AudioEvent AddEvent(Vector2 outputPos) 78 | { 79 | AudioEvent newEvent = ScriptableObject.CreateInstance(); 80 | newEvent.name = "New Audio Event"; 81 | AssetDatabase.AddObjectToAsset(newEvent, this); 82 | newEvent.InitializeEvent(outputPos); 83 | this.audioEvents.Add(newEvent); 84 | EditorUtility.SetDirty(this); 85 | AssetDatabase.SaveAssets(); 86 | return newEvent; 87 | } 88 | 89 | /// 90 | /// Destroy an event object and remove it from the array of events 91 | /// 92 | /// 93 | public void DeleteEvent(AudioEvent eventToDelete) 94 | { 95 | eventToDelete.DeleteNodes(); 96 | this.audioEvents.Remove(eventToDelete); 97 | ScriptableObject.DestroyImmediate(eventToDelete, true); 98 | } 99 | 100 | /// 101 | /// Create a new AudioParameter and add it to the array of parameters 102 | /// 103 | /// The AudioParameter instance that was created 104 | public AudioParameter AddParameter() 105 | { 106 | AudioParameter newParameter = ScriptableObject.CreateInstance(); 107 | newParameter.name = "New Audio Parameter"; 108 | newParameter.InitializeParameter(); 109 | AssetDatabase.AddObjectToAsset(newParameter, this); 110 | this.parameters.Add(newParameter); 111 | EditorUtility.SetDirty(this); 112 | AssetDatabase.SaveAssets(); 113 | return newParameter; 114 | } 115 | 116 | /// 117 | /// Destroy an AudioParameter and remove it from the array of parameters 118 | /// 119 | /// The AudioParameter you wish to delete 120 | public void DeleteParameter(AudioParameter parameterToDelete) 121 | { 122 | this.parameters.Remove(parameterToDelete); 123 | ScriptableObject.DestroyImmediate(parameterToDelete, true); 124 | } 125 | 126 | public AudioSwitch AddSwitch() 127 | { 128 | AudioSwitch newSwitch = ScriptableObject.CreateInstance(); 129 | newSwitch.name = "New Audio Switch"; 130 | newSwitch.InitializeSwitch(); 131 | AssetDatabase.AddObjectToAsset(newSwitch, this); 132 | this.switches.Add(newSwitch); 133 | EditorUtility.SetDirty(this); 134 | AssetDatabase.SaveAssets(); 135 | return newSwitch; 136 | } 137 | 138 | public void DeleteSwitch(AudioSwitch switchToDelete) 139 | { 140 | this.switches.Remove(switchToDelete); 141 | ScriptableObject.DestroyImmediate(switchToDelete, true); 142 | } 143 | 144 | public void SortEvents() 145 | { 146 | this.audioEvents.Sort(new AudioEventComparer()); 147 | } 148 | #endif 149 | } 150 | } -------------------------------------------------------------------------------- /Runtime/AudioBank.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc9d501b83c3a5d45b5deffcc726951f 3 | timeCreated: 1518568659 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioBlendContainer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | public class AudioBlendContainer : AudioNode 12 | { 13 | /// 14 | /// Randomly select a connected node 15 | /// 16 | /// The existing runtime audio event 17 | public override void ProcessNode(ActiveEvent activeEvent) 18 | { 19 | if (this.input.ConnectedNodes == null || this.input.ConnectedNodes.Length == 0) 20 | { 21 | Debug.LogWarningFormat("No connected nodes for {0}", this.name); 22 | return; 23 | } 24 | 25 | for (int i = 0; i < this.input.ConnectedNodes.Length; i++) 26 | { 27 | ProcessConnectedNode(i, activeEvent); 28 | } 29 | } 30 | 31 | #if UNITY_EDITOR 32 | 33 | /// 34 | /// EDITOR: Set the initial values for the node's properties 35 | /// 36 | /// The position of the node on the graph 37 | public override void InitializeNode(Vector2 position) 38 | { 39 | this.name = "Blend Container"; 40 | this.nodeRect.height = 50; 41 | this.nodeRect.width = 150; 42 | this.nodeRect.position = position; 43 | AddInput(); 44 | AddOutput(); 45 | } 46 | 47 | #endif 48 | } 49 | } -------------------------------------------------------------------------------- /Runtime/AudioBlendContainer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b0ff48ff47e3dbd44b049486f647913c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioBlendFile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | public class AudioBlendFile : AudioNode 12 | { 13 | /// 14 | /// The audio clip to be set on the AudioSource if this node is processed 15 | /// 16 | [SerializeField] 17 | private AudioClip file = null; 18 | [SerializeField] 19 | private AudioParameter parameter = null; 20 | [SerializeField] 21 | private AnimationCurve responseCurve = new AnimationCurve(); 22 | /// 23 | /// The minimum start position of the node 24 | /// 25 | [Range(0, 1)] 26 | public float minStartTime = 0; 27 | /// 28 | /// The maximum start position of the node 29 | /// 30 | [Range(0, 1)] 31 | public float maxStartTime = 0; 32 | /// 33 | /// The Start time for the audio file to stay playing at 34 | /// 35 | public float startTime { get; private set; } 36 | 37 | /// 38 | /// Apply all modifications to the ActiveEvent before it gets played 39 | /// 40 | /// The runtime event being prepared for playback 41 | public override void ProcessNode(ActiveEvent activeEvent) 42 | { 43 | if (this.file == null) 44 | { 45 | Debug.LogWarningFormat("No file in node {0}", this.name); 46 | return; 47 | } 48 | 49 | RandomStartTime(); 50 | activeEvent.AddEventSource(this.file, this.parameter, this.responseCurve, this.startTime); 51 | } 52 | 53 | /// 54 | /// If the min and max start time are not the same, then generate a random value between min and max start time. 55 | /// 56 | /// 57 | public void RandomStartTime() 58 | { 59 | if (this.minStartTime != this.maxStartTime) 60 | { 61 | this.startTime = UnityEngine.Random.Range(minStartTime, maxStartTime); 62 | } 63 | else 64 | { 65 | this.startTime = minStartTime; 66 | } 67 | 68 | this.startTime = file.length * startTime; 69 | } 70 | 71 | #if UNITY_EDITOR 72 | 73 | public AudioClip File 74 | { 75 | get { return this.file; } 76 | set { this.file = value; } 77 | } 78 | 79 | /// 80 | /// The width in pixels for the node's window in the graph 81 | /// 82 | private const float NodeWidth = 300; 83 | private const float NodeHeight = 110; 84 | 85 | /// 86 | /// EDITOR: Initialize the node's properties when it is first created 87 | /// 88 | /// The position of the new node in the graph 89 | public override void InitializeNode(Vector2 position) 90 | { 91 | this.name = "Blend File"; 92 | this.nodeRect.position = position; 93 | this.nodeRect.width = NodeWidth; 94 | this.nodeRect.height = NodeHeight; 95 | AddOutput(); 96 | EditorUtility.SetDirty(this); 97 | } 98 | 99 | /// 100 | /// EDITOR: Display the node's properties in the graph 101 | /// 102 | protected override void DrawProperties() 103 | { 104 | this.file = EditorGUILayout.ObjectField(this.file, typeof(AudioClip), false) as AudioClip; 105 | if (this.file != null && this.name != this.file.name) 106 | { 107 | this.name = this.file.name; 108 | } 109 | this.parameter = EditorGUILayout.ObjectField(this.parameter, typeof(AudioParameter), false) as AudioParameter; 110 | this.responseCurve = EditorGUILayout.CurveField("Blend Curve", this.responseCurve); 111 | EditorGUILayout.MinMaxSlider("Start Time", ref this.minStartTime, ref this.maxStartTime, 0, 1); 112 | } 113 | 114 | #endif 115 | } 116 | } -------------------------------------------------------------------------------- /Runtime/AudioBlendFile.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7dc155598a1eb0249b8a6c2db878956d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioDebugMessage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | /// 12 | /// Prints a message to Unity's console log 13 | /// 14 | public class AudioDebugMessage : AudioNode 15 | { 16 | /// 17 | /// The text to print to Unity's console when the node is processed 18 | /// 19 | [SerializeField, Multiline] 20 | private string message = ""; 21 | 22 | /// 23 | /// Print out the message and move on to the next node 24 | /// 25 | /// 26 | public override void ProcessNode(ActiveEvent activeEvent) 27 | { 28 | Debug.Log(this.message, activeEvent.emitterTransform); 29 | 30 | ProcessConnectedNode(0, activeEvent); 31 | } 32 | 33 | #if UNITY_EDITOR 34 | 35 | /// 36 | /// EDITOR: Set the initial properties on the node 37 | /// 38 | /// 39 | public override void InitializeNode(Vector2 position) 40 | { 41 | this.name = "Debug Message"; 42 | this.nodeRect.height = 50; 43 | this.nodeRect.width = 250; 44 | this.nodeRect.position = position; 45 | AddInput(); 46 | AddOutput(); 47 | } 48 | 49 | /// 50 | /// EDITOR: Draw the text field for the node's message 51 | /// 52 | protected override void DrawProperties() 53 | { 54 | this.message = EditorGUILayout.TextField(this.message, EditorStyles.textArea); 55 | } 56 | 57 | #endif 58 | } 59 | } -------------------------------------------------------------------------------- /Runtime/AudioDebugMessage.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 264c403d8bc4e0f439a4a5526f6d9e9a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioDelay.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | /// 12 | /// Adds a time delay in the playback of an event 13 | /// 14 | public class AudioDelay : AudioNode 15 | { 16 | /// 17 | /// The amount of time in seconds to delay the playback of the audio file 18 | /// 19 | [SerializeField] 20 | private float delaySeconds = 0; 21 | 22 | /// 23 | /// Add the node's delay to the total delay of the event 24 | /// 25 | /// 26 | public override void ProcessNode(ActiveEvent activeEvent) 27 | { 28 | activeEvent.initialDelay += this.delaySeconds; 29 | 30 | ProcessConnectedNode(0, activeEvent); 31 | } 32 | 33 | #if UNITY_EDITOR 34 | 35 | /// 36 | /// Set the initial properties on the node when it is first created 37 | /// 38 | /// 39 | public override void InitializeNode(Vector2 position) 40 | { 41 | this.name = "Delay"; 42 | this.nodeRect.height = 50; 43 | this.nodeRect.width = 200; 44 | this.nodeRect.position = position; 45 | AddInput(); 46 | AddOutput(); 47 | } 48 | 49 | /// 50 | /// Draw the float field property for the node in the graph 51 | /// 52 | protected override void DrawProperties() 53 | { 54 | this.delaySeconds = EditorGUILayout.FloatField("Seconds", this.delaySeconds); 55 | } 56 | 57 | #endif 58 | } 59 | } -------------------------------------------------------------------------------- /Runtime/AudioDelay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7d62fcd603a4e0c4ca48318393a2210e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioEvent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | using System.Collections.Generic; 6 | #if UNITY_EDITOR 7 | using UnityEditor; 8 | #endif 9 | 10 | namespace Microsoft.MixedReality.Toolkit.Audio 11 | { 12 | /// 13 | /// The logic, settings, and audio clips that define the playback of a sound 14 | /// 15 | public class AudioEvent : ScriptableObject 16 | { 17 | /// 18 | /// The maximum number of simultaneous instances of an event that can be played 19 | /// 20 | [SerializeField] 21 | private int instanceLimit = 0; 22 | 23 | /// 24 | /// The amount of time in seconds for the event to fade in from a volume of 0 25 | /// 26 | [SerializeField] 27 | private float fadeIn = 0; 28 | 29 | /// 30 | /// The amount of time in seconds for the event to fade out to a volume of 0 31 | /// If the event is not explicitly stopped, the fade out will start before the end of the audio file 32 | /// 33 | [SerializeField] 34 | private float fadeOut = 0; 35 | 36 | /// 37 | /// The group of events to stop when this event is played 38 | /// 39 | [SerializeField] 40 | private int group = 0; 41 | 42 | /// 43 | /// The nodes that determine the logic of an AudioEvent 44 | /// 45 | [SerializeField] 46 | private List nodes = new List(); 47 | 48 | /// 49 | /// The final node in an AudioEvent that sets AudioSource properties 50 | /// 51 | [SerializeField] 52 | private AudioOutput output; 53 | 54 | /// 55 | /// The parameters that affect the ActiveEvent when it is playing 56 | /// 57 | [SerializeField] 58 | private List parameters = new List(); 59 | 60 | /// 61 | /// The maximum number of simultaneous instances of an event that can be played 62 | /// 63 | public int InstanceLimit 64 | { 65 | get { return this.instanceLimit; } 66 | set { this.instanceLimit = value; } 67 | } 68 | 69 | /// 70 | /// Internal AudioManager use: accessor for the group this event belongs to 71 | /// 72 | public int Group 73 | { 74 | get { return this.group; } 75 | set { group = value; } 76 | } 77 | 78 | public bool ValidateAudioFiles() 79 | { 80 | for (int i = 0; i < this.nodes.Count; i++) 81 | { 82 | AudioFile tempNode = this.nodes[i] as AudioFile; 83 | 84 | if (tempNode != null) 85 | { 86 | if (tempNode.File != null) 87 | { 88 | if (tempNode.File.length <= 0) 89 | { 90 | Debug.LogErrorFormat("Invalid clip length in node {0}, event: {1}", tempNode.name, this.name); 91 | return false; 92 | } 93 | } 94 | else 95 | { 96 | Debug.LogErrorFormat("Null clip in node {0}, event: {1}", tempNode.name, this.name); 97 | return false; 98 | } 99 | } 100 | } 101 | 102 | return true; 103 | } 104 | 105 | /// 106 | /// Internal AudioManager use: play the event using a pre-existing ActiveEvent 107 | /// 108 | /// The ActiveEvent for the AudioManager to update and track currently playing events 109 | public void SetActiveEventProperties(ActiveEvent activeEvent) 110 | { 111 | this.output.ProcessNode(activeEvent); 112 | } 113 | 114 | /// 115 | /// Clear all nonserialized modifications the event 116 | /// 117 | public void Reset() 118 | { 119 | if (this.nodes == null) 120 | { 121 | return; 122 | } 123 | 124 | for (int i = 0; i < this.nodes.Count; i++) 125 | { 126 | this.nodes[i].Reset(); 127 | } 128 | } 129 | 130 | #region EDITOR 131 | 132 | #if UNITY_EDITOR 133 | 134 | /// 135 | /// EDITOR: Initialize the required components of a new AudioEvent when it is first created 136 | /// 137 | /// The position of the Output node in the canvas 138 | public void InitializeEvent(Vector2 outputPos) 139 | { 140 | AudioOutput tempNode = ScriptableObject.CreateInstance(); 141 | AssetDatabase.AddObjectToAsset(tempNode, this); 142 | tempNode.InitializeNode(outputPos); 143 | this.output = tempNode; 144 | this.nodes.Add(tempNode); 145 | EditorUtility.SetDirty(this); 146 | } 147 | 148 | /// 149 | /// EDITOR: Add a created node to the event 150 | /// 151 | /// The node to add 152 | public void AddNode(AudioNode newNode) 153 | { 154 | this.nodes.Add(newNode); 155 | } 156 | 157 | /// 158 | /// EDITOR: Destroy all nodes (including Output) and clear their connections 159 | /// 160 | public void DeleteNodes() 161 | { 162 | for (int i = 0; i < this.nodes.Count; i++) 163 | { 164 | this.nodes[i].DeleteConnections(); 165 | ScriptableObject.DestroyImmediate(this.nodes[i], true); 166 | } 167 | } 168 | 169 | /// 170 | /// EDITOR: Remove and destroy a node (except Output) 171 | /// 172 | /// The node you wish to delete 173 | public void DeleteNode(AudioNode nodeToDelete) 174 | { 175 | if (nodeToDelete == null) 176 | { 177 | Debug.LogWarning("Trying to remove null node!"); 178 | return; 179 | } 180 | else if (nodeToDelete == this.output) 181 | { 182 | Debug.LogWarning("Trying to delete output node!"); 183 | return; 184 | } 185 | 186 | for (int i = 0; i < this.nodes.Count; i++) 187 | { 188 | AudioNode tempNode = this.nodes[i]; 189 | if (tempNode != nodeToDelete) 190 | { 191 | if (tempNode.Input != null && nodeToDelete.Output != null) 192 | { 193 | tempNode.Input.RemoveConnection(nodeToDelete.Output); 194 | } 195 | } 196 | } 197 | 198 | nodeToDelete.DeleteConnections(); 199 | this.nodes.Remove(nodeToDelete); 200 | ScriptableObject.DestroyImmediate(nodeToDelete, true); 201 | EditorUtility.SetDirty(this); 202 | } 203 | 204 | /// 205 | /// EDITOR: Add an empty parameter 206 | /// 207 | public void AddParameter() 208 | { 209 | if (this.parameters == null) 210 | { 211 | this.parameters = new List(); 212 | } 213 | 214 | this.parameters.Add(new AudioEventParameter()); 215 | } 216 | 217 | /// 218 | /// EDITOR: Remove a parameter from the event 219 | /// 220 | /// The parameter you wish to remove 221 | public void DeleteParameter(AudioEventParameter parameterToDelete) 222 | { 223 | this.parameters.Remove(parameterToDelete); 224 | } 225 | 226 | /// 227 | /// EDITOR: Draw the parameters section of the event editor 228 | /// 229 | public void DrawParameters() 230 | { 231 | if (GUILayout.Button("Add Parameter")) 232 | { 233 | AddParameter(); 234 | } 235 | 236 | if (this.parameters == null) 237 | { 238 | this.parameters = new List(); 239 | } 240 | 241 | for (int i = 0; i < this.parameters.Count; i++) 242 | { 243 | AudioEventParameter tempParam = this.parameters[i]; 244 | tempParam.parameter = EditorGUILayout.ObjectField(tempParam.parameter, typeof(AudioParameter), false) as AudioParameter; 245 | tempParam.responseCurve = EditorGUILayout.CurveField("Curve", tempParam.responseCurve); 246 | tempParam.paramType = (ParameterType)EditorGUILayout.EnumPopup("Property", tempParam.paramType); 247 | if (GUILayout.Button("Delete Parameter")) 248 | { 249 | DeleteParameter(tempParam); 250 | } 251 | EditorGUILayout.Separator(); 252 | } 253 | } 254 | 255 | #endif 256 | 257 | /// 258 | /// Time in seconds for the event to fade in from a volume of 0 259 | /// 260 | public float FadeIn 261 | { 262 | get { return this.fadeIn; } 263 | #if UNITY_EDITOR 264 | set { this.fadeIn = value; } 265 | #endif 266 | } 267 | 268 | /// 269 | /// Time in seconds for the event to fade out 270 | /// 271 | public float FadeOut 272 | { 273 | get { return this.fadeOut; } 274 | set { this.fadeOut = value; } 275 | } 276 | 277 | /// 278 | /// Public accessor for the list of all nodes in the event 279 | /// 280 | public List EditorNodes 281 | { 282 | get { return this.nodes; } 283 | } 284 | 285 | /// 286 | /// Public accessor for the Output node reference 287 | /// 288 | public AudioOutput Output 289 | { 290 | get { return this.output; } 291 | set { this.output = value; } 292 | } 293 | 294 | /// 295 | /// Public accessor for the list of parameters modifying the event at runtime 296 | /// 297 | public List Parameters 298 | { 299 | get 300 | { 301 | return this.parameters; 302 | } 303 | } 304 | 305 | #endregion 306 | } 307 | 308 | public sealed class AudioEventComparer : IComparer 309 | { 310 | public int Compare(AudioEvent x, AudioEvent y) 311 | { 312 | if (object.ReferenceEquals(x, y)) 313 | return 0; 314 | else if (x == null) 315 | return -1; 316 | else if (y == null) 317 | return -1; 318 | else 319 | return string.Compare(x.name, y.name); 320 | } 321 | } 322 | } -------------------------------------------------------------------------------- /Runtime/AudioEvent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b8d6a001556e463478b5c476e3e6195c 3 | timeCreated: 1518568359 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioEventParameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | 6 | namespace Microsoft.MixedReality.Toolkit.Audio 7 | { 8 | /// 9 | /// An AudioParameter with a response curve and audio property to apply changes to 10 | /// 11 | [System.Serializable] 12 | public class AudioEventParameter 13 | { 14 | /// 15 | /// The root parameter that the event is using 16 | /// 17 | public AudioParameter parameter = null; 18 | /// 19 | /// The curve to evaluate the parameter's value on 20 | /// 21 | public AnimationCurve responseCurve = new AnimationCurve(); 22 | /// 23 | /// Which audio property the parameter affects 24 | /// 25 | public ParameterType paramType; 26 | 27 | /// 28 | /// The current value of the parameter before being evaluated by the response curve 29 | /// 30 | public float CurrentValue { get; private set; } 31 | /// 32 | /// The resulting value from evaluating the CurrentValue on the response curve 33 | /// 34 | public float CurrentResult { get; private set; } 35 | 36 | /// 37 | /// Reset the parameter to sync it back with the root parameter 38 | /// 39 | public void SyncParameter() 40 | { 41 | this.CurrentValue = this.parameter.CurrentValue; 42 | ProcessParameter(); 43 | } 44 | 45 | /// 46 | /// Evaluate the result of the current value on the response curve 47 | /// 48 | public void ProcessParameter() 49 | { 50 | this.CurrentResult = this.responseCurve.Evaluate(this.parameter.CurrentValue); 51 | } 52 | 53 | /// 54 | /// Evaluate a custom value on the parameter's response curve 55 | /// 56 | /// The custom value to evaluate 57 | /// The result of the newValue on the parameter's response curve 58 | public float ProcessParameter(float newValue) 59 | { 60 | return this.responseCurve.Evaluate(newValue); 61 | } 62 | } 63 | 64 | /// 65 | /// The audio properties that a parameter can affect 66 | /// 67 | public enum ParameterType 68 | { 69 | Volume, 70 | Pitch 71 | } 72 | } -------------------------------------------------------------------------------- /Runtime/AudioEventParameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a50129ef39c52264aaa7ef2617f00b9a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioEventRouter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using Microsoft.MixedReality.Toolkit.Audio; 5 | 6 | public class AudioEventRouter : MonoBehaviour 7 | { 8 | public AudioTrigger[] triggers; 9 | private List activeOnEnabledEvents = new List(); 10 | private const float MIN_LOOP_TIME = 1.0f; 11 | private const float MAX_LOOP_TIME = 1000.0f; 12 | 13 | public void OnEnable() 14 | { 15 | for (int i = 0; i < this.triggers.Length; i++) 16 | { 17 | AudioTrigger tempTrigger = this.triggers[i]; 18 | if (tempTrigger.triggerOnEvent == UnityTrigger.OnEnable) 19 | { 20 | if (tempTrigger.loopTrigger) 21 | { 22 | StartLoopingTrigger(i); 23 | } 24 | else 25 | { 26 | this.activeOnEnabledEvents.Add(PlayTraceableTrigger(i)); 27 | } 28 | } 29 | } 30 | } 31 | 32 | public void OnDisable() 33 | { 34 | for (int i = 0; i < this.triggers.Length; i++) 35 | { 36 | AudioTrigger tempTrigger = this.triggers[i]; 37 | if (tempTrigger.triggerOnEvent == UnityTrigger.OnDisable) 38 | { 39 | PlayAudioTrigger(i); 40 | } 41 | 42 | StopEnabledEvents(); 43 | } 44 | } 45 | 46 | public void PlayAudioEvent(AudioEvent eventToPlay) 47 | { 48 | AudioManager.PlayEvent(eventToPlay, this.gameObject); 49 | } 50 | 51 | public void PlayAudioTrigger(int triggerNum) 52 | { 53 | PlayTraceableTrigger(triggerNum); 54 | } 55 | 56 | public void StartLoopingTrigger(int triggerNum) 57 | { 58 | StartCoroutine(PlayLoopingTrigger(triggerNum)); 59 | } 60 | 61 | public void StopEvents(AudioEvent eventToStop) 62 | { 63 | AudioManager.StopAll(eventToStop); 64 | } 65 | 66 | public void StopEvents(int groupNumber) 67 | { 68 | AudioManager.StopAll(groupNumber); 69 | } 70 | 71 | public void StopEnabledEvents() 72 | { 73 | for (int i = 0; i < this.activeOnEnabledEvents.Count; i++) 74 | { 75 | ActiveEvent tempEvent = this.activeOnEnabledEvents[i]; 76 | if (tempEvent != null) 77 | { 78 | tempEvent.Stop(); 79 | } 80 | } 81 | 82 | this.activeOnEnabledEvents.Clear(); 83 | } 84 | 85 | private ActiveEvent PlayTraceableTrigger(int triggerNum) 86 | { 87 | AudioTrigger tempTrigger = triggers[triggerNum]; 88 | if (!tempTrigger.usePosition && tempTrigger.soundEmitter == null) 89 | { 90 | return AudioManager.PlayEvent(tempTrigger.eventToTrigger, this.gameObject); 91 | } 92 | else if (tempTrigger.usePosition) 93 | { 94 | return AudioManager.PlayEvent(tempTrigger.eventToTrigger, tempTrigger.soundPosition); 95 | } 96 | else 97 | { 98 | return AudioManager.PlayEvent(tempTrigger.eventToTrigger, tempTrigger.soundEmitter); 99 | } 100 | } 101 | 102 | private IEnumerator PlayLoopingTrigger(int triggerNum) 103 | { 104 | AudioTrigger loopTrigger = triggers[triggerNum]; 105 | loopTrigger.loopTimeMin = Mathf.Clamp(loopTrigger.loopTimeMin, MIN_LOOP_TIME, MAX_LOOP_TIME); 106 | loopTrigger.loopTimeMax = Mathf.Clamp(loopTrigger.loopTimeMax, MIN_LOOP_TIME, MAX_LOOP_TIME); 107 | while (this.enabled) 108 | { 109 | PlayAudioTrigger(triggerNum); 110 | float timeUntilNextLoop = Random.Range(loopTrigger.loopTimeMin, loopTrigger.loopTimeMax); 111 | yield return new WaitForSeconds(timeUntilNextLoop); 112 | } 113 | } 114 | } 115 | 116 | [System.Serializable] 117 | public class AudioTrigger 118 | { 119 | public AudioEvent eventToTrigger = null; 120 | public bool usePosition = false; 121 | public Vector3 soundPosition = Vector3.zero; 122 | public GameObject soundEmitter = null; 123 | public UnityTrigger triggerOnEvent = UnityTrigger.None; 124 | public bool loopTrigger = false; 125 | public float loopTimeMin = 0f; 126 | public float loopTimeMax = 0f; 127 | } 128 | 129 | public enum UnityTrigger 130 | { 131 | None, 132 | OnEnable, 133 | OnDisable, 134 | OnSliderUpdate 135 | } -------------------------------------------------------------------------------- /Runtime/AudioEventRouter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 486d0524fdbbb1546bb889ca3f4fd2fd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioFile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | using System; 6 | #if UNITY_EDITOR 7 | using UnityEditor; 8 | #endif 9 | 10 | namespace Microsoft.MixedReality.Toolkit.Audio 11 | { 12 | /// 13 | /// An AudioNode containing a reference to an AudioClip 14 | /// 15 | public class AudioFile : AudioNode 16 | { 17 | /// 18 | /// The audio clip to be set on the AudioSource if this node is processed 19 | /// 20 | [SerializeField] 21 | private AudioClip file = null; 22 | 23 | public AudioClip File 24 | { 25 | get { return this.file; } 26 | set { this.file = value; } 27 | } 28 | 29 | /// 30 | /// The amount of volume change to apply if this node is processed 31 | /// 32 | [SerializeField, Range(-1, 1)] 33 | private float volumeOffset = 0; 34 | /// 35 | /// The amount of pitch change to apply if this node is processed 36 | /// 37 | [SerializeField, Range(-1, 1)] 38 | private float pitchOffset = 0; 39 | /// 40 | /// The minimum start position of the node 41 | /// 42 | [Range(0, 1)] 43 | public float minStartTime = 0; 44 | /// 45 | /// The maximum start position of the node 46 | /// 47 | [Range(0, 1)] 48 | public float maxStartTime = 0; 49 | /// 50 | /// The Start time for the audio file to stay playing at 51 | /// 52 | public float startTime { get; private set; } 53 | 54 | /// 55 | /// Apply all modifications to the ActiveEvent before it gets played 56 | /// 57 | /// The runtime event being prepared for playback 58 | public override void ProcessNode(ActiveEvent activeEvent) 59 | { 60 | if (this.file == null) 61 | { 62 | Debug.LogWarningFormat("No file in node {0}, Event: {1}", this.name, activeEvent.rootEvent.name); 63 | return; 64 | } 65 | else if (this.file.length <= 0) 66 | { 67 | Debug.LogWarningFormat("Invalid file length for node {0}, Event: {1}", this.name, activeEvent.rootEvent.name); 68 | return; 69 | } 70 | 71 | activeEvent.ModulateVolume(this.volumeOffset); 72 | activeEvent.ModulatePitch(this.pitchOffset); 73 | RandomStartTime(); 74 | activeEvent.AddEventSource(this.file, null, null, startTime); 75 | } 76 | 77 | /// 78 | /// If the min and max start time are not the same, then generate a random value between min and max start time. 79 | /// 80 | /// 81 | public void RandomStartTime() 82 | { 83 | if (minStartTime != maxStartTime) 84 | { 85 | startTime = UnityEngine.Random.Range(minStartTime, maxStartTime); 86 | } 87 | else 88 | { 89 | startTime = minStartTime; 90 | } 91 | 92 | startTime = file.length * startTime; 93 | } 94 | 95 | #if UNITY_EDITOR 96 | 97 | /// 98 | /// The width in pixels for the node's window in the graph 99 | /// 100 | private const float NodeWidth = 300; 101 | private const float NodeHeight = 110; 102 | 103 | /// 104 | /// EDITOR: Initialize the node's properties when it is first created 105 | /// 106 | /// The position of the new node in the graph 107 | public override void InitializeNode(Vector2 position) 108 | { 109 | this.name = "Audio File"; 110 | this.nodeRect.position = position; 111 | this.nodeRect.width = NodeWidth; 112 | this.nodeRect.height = NodeHeight; 113 | AddOutput(); 114 | EditorUtility.SetDirty(this); 115 | } 116 | 117 | /// 118 | /// EDITOR: Display the node's properties in the graph 119 | /// 120 | protected override void DrawProperties() 121 | { 122 | this.file = EditorGUILayout.ObjectField(this.file, typeof(AudioClip), false) as AudioClip; 123 | if (this.file != null && this.name != this.file.name) 124 | { 125 | this.name = this.file.name; 126 | } 127 | this.volumeOffset = EditorGUILayout.Slider("Volume Offset", this.volumeOffset, -1, 1); 128 | this.pitchOffset = EditorGUILayout.Slider("Pitch Offset", this.pitchOffset, -1, 1); 129 | EditorGUILayout.MinMaxSlider("Start Time", ref this.minStartTime, ref this.maxStartTime, 0, 1); 130 | } 131 | 132 | #endif 133 | } 134 | } -------------------------------------------------------------------------------- /Runtime/AudioFile.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 563994e88e1b8414890b10d6bac7a456 3 | timeCreated: 1518568549 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioLanguageSelector.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | 6 | namespace Microsoft.MixedReality.Toolkit.Audio 7 | { 8 | /// 9 | /// An AudioNode for branching an AudioEvent based on the AudioManager's language setting 10 | /// 11 | public class AudioLanguageSelector : AudioNode 12 | { 13 | /// 14 | /// Select a node with the current language in the AudioManager 15 | /// 16 | /// The existing runtime audio event 17 | public override void ProcessNode(ActiveEvent activeEvent) 18 | { 19 | if (this.input.ConnectedNodes == null || this.input.ConnectedNodes.Length == 0) 20 | { 21 | Debug.LogWarningFormat("No connected nodes for {0}", this.name); 22 | return; 23 | } 24 | 25 | for (int i = 0; i < this.input.ConnectedNodes.Length; i++) 26 | { 27 | AudioNode tempNode = this.input.ConnectedNodes[i].ParentNode; 28 | if (tempNode.GetType() == typeof(AudioVoiceFile)) 29 | { 30 | AudioVoiceFile voiceNode = (AudioVoiceFile)tempNode; 31 | if (voiceNode.Language == AudioManager.CurrentLanguage) 32 | { 33 | ProcessConnectedNode(i, activeEvent); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | Debug.LogErrorFormat("AudioManager: Event \"{0}\" not localized for language: {1}", activeEvent.name, AudioManager.CurrentLanguage); 40 | } 41 | 42 | #if UNITY_EDITOR 43 | 44 | /// 45 | /// EDITOR: Set the initial values for the node's properties 46 | /// 47 | /// The position of the node on the graph 48 | public override void InitializeNode(Vector2 position) 49 | { 50 | this.name = "Language Selector"; 51 | this.nodeRect.height = 50; 52 | this.nodeRect.width = 150; 53 | this.nodeRect.position = position; 54 | AddInput(); 55 | AddOutput(); 56 | } 57 | 58 | #endif 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /Runtime/AudioLanguageSelector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 45afd7b3b900067479ca6a777c71e8ca 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | using System.Globalization; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | /// 12 | /// The manager that handles the playback of AudioEvents 13 | /// 14 | public class AudioManager : MonoBehaviour 15 | { 16 | /// 17 | /// Singleton instance of the audio manager 18 | /// 19 | private static AudioManager Instance; 20 | /// 21 | /// Flag for creating instances, set to false during shutdown 22 | /// 23 | private static bool AllowCreateInstance = true; 24 | /// 25 | /// The currently-playing events at runtime 26 | /// 27 | public static List ActiveEvents { get; private set; } 28 | /// 29 | /// The AudioSource components that have been added by a previously-played event 30 | /// 31 | private static readonly List AvailableSources = new List(); 32 | /// 33 | /// List of the previously started events 34 | /// 35 | private static readonly List previousEvents = new List(); 36 | /// 37 | /// The language that all voice events should play in 38 | /// 39 | public static int CurrentLanguage { get; private set; } 40 | /// 41 | /// The full list of languages available 42 | /// 43 | public static string[] Languages; 44 | /// 45 | /// The default number of AudioSources to create in the pool 46 | /// 47 | private static int DefaultSourcesCount = 80; 48 | /// 49 | /// The total list of AudioSources for the manager to use 50 | /// 51 | private static readonly List SourcePool = new List(); 52 | private static bool debugMode = false; 53 | public static bool DebugMode => debugMode; 54 | private const int MaxPreviousEvents = 300; 55 | public static List PreviousEvents => previousEvents; 56 | 57 | float lastTime = 0f; 58 | 59 | private readonly struct ActiveEventRemovalTimestamp 60 | { 61 | public readonly float RemovalTime; 62 | public readonly ActiveEvent ActiveEvent; 63 | 64 | public ActiveEventRemovalTimestamp(float removalTime, ActiveEvent activeEvent) 65 | { 66 | RemovalTime = removalTime; 67 | ActiveEvent = activeEvent; 68 | } 69 | } 70 | 71 | // We could get fancy and sort, but this list is usually pretty small, and the sort call can be expensive 72 | private readonly List delayedEventsToRemove = new(); 73 | 74 | 75 | #region Interface 76 | 77 | /// 78 | /// Start playing an AudioEvent 79 | /// 80 | /// The AudioEvent to play 81 | /// The GameObject to play the event on 82 | /// The reference for the runtime event that can be modified or stopped explicitly 83 | public static ActiveEvent PlayEvent(AudioEvent eventToPlay, GameObject emitterObject) 84 | { 85 | if (!ValidateManager() || !ValidateEvent(eventToPlay)) 86 | { 87 | return null; 88 | } 89 | 90 | ActiveEvent tempEvent = new ActiveEvent(eventToPlay, emitterObject.transform); 91 | tempEvent.Play(); 92 | 93 | return tempEvent; 94 | } 95 | 96 | public static ActiveEvent PlayEvent(AudioEvent eventToPlay, Vector3 position) 97 | { 98 | if (!ValidateManager() || !ValidateEvent(eventToPlay)) 99 | { 100 | return null; 101 | } 102 | 103 | ActiveEvent tempEvent = new ActiveEvent(eventToPlay, null); 104 | tempEvent.Play(); 105 | tempEvent.SetAllSourcePositions(position); 106 | 107 | return tempEvent; 108 | } 109 | 110 | /// 111 | /// Start playing an AudioEvent 112 | /// 113 | /// The AudioEvent to play 114 | /// The AudioSource component to play the event on 115 | /// The reference for the runtime event that can be modified or stopped explicitly 116 | public static ActiveEvent PlayEvent(AudioEvent eventToPlay, AudioSource emitter) 117 | { 118 | Debug.LogWarningFormat("AudioManager: deprecated function called on event {0} - play on an AudioSource no longer supported"); 119 | if (!ValidateManager() || !ValidateEvent(eventToPlay)) 120 | { 121 | return null; 122 | } 123 | 124 | ActiveEvent tempEvent = new ActiveEvent(eventToPlay, emitter.transform); 125 | tempEvent.Play(); 126 | 127 | return tempEvent; 128 | } 129 | 130 | /// 131 | /// Stop all active instances of an audio event 132 | /// 133 | /// The event to stop all instances of 134 | public static void StopAll(AudioEvent eventsToStop) 135 | { 136 | if (!ValidateManager()) 137 | { 138 | return; 139 | } 140 | 141 | for (int i = ActiveEvents.Count - 1; i >= 0; i--) 142 | { 143 | ActiveEvent tempEvent = ActiveEvents[i]; 144 | if (tempEvent.rootEvent == eventsToStop) 145 | { 146 | tempEvent.Stop(); 147 | } 148 | } 149 | } 150 | 151 | /// 152 | /// Stop all active instances of a group 153 | /// 154 | /// The group number to stop all instances of 155 | public static void StopAll(int groupNum) 156 | { 157 | if (!ValidateManager()) 158 | { 159 | return; 160 | } 161 | 162 | for (int i = ActiveEvents.Count - 1; i >= 0; i--) 163 | { 164 | ActiveEvent tempEvent = ActiveEvents[i]; 165 | if (tempEvent.rootEvent.Group == groupNum) 166 | { 167 | tempEvent.Stop(); 168 | } 169 | } 170 | } 171 | 172 | /// 173 | /// Clear an ActiveEvent from the list of ActiveEvents 174 | /// 175 | /// The event that is no longer playing to remove from the ActiveEvent list 176 | public static void RemoveActiveEvent(ActiveEvent stoppedEvent) 177 | { 178 | if (!ValidateManager()) 179 | { 180 | return; 181 | } 182 | 183 | List sources = stoppedEvent.sources; 184 | for (int i = 0; i < sources.Count; i++) 185 | { 186 | AudioSource tempSource = sources[i].source; 187 | if (!AvailableSources.Contains(tempSource)) 188 | { 189 | AvailableSources.Add(tempSource); 190 | } 191 | } 192 | 193 | ActiveEvents.Remove(stoppedEvent); 194 | stoppedEvent = null; 195 | } 196 | 197 | public static void AddPreviousEvent(ActiveEvent newEvent) 198 | { 199 | previousEvents.Add(newEvent); 200 | 201 | while (previousEvents.Count > MaxPreviousEvents) 202 | { 203 | previousEvents.RemoveAt(0); 204 | } 205 | } 206 | 207 | /// 208 | /// Get the list of all cultures for compatible languges 209 | /// 210 | public static void UpdateLanguages() 211 | { 212 | CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); 213 | Languages = new string[cultures.Length]; 214 | for (int i = 0; i < cultures.Length; i++) 215 | { 216 | Languages[i] = cultures[i].Name; 217 | } 218 | } 219 | 220 | public static void SetDebugMode(bool toggle) 221 | { 222 | debugMode = toggle; 223 | ClearSourceText(); 224 | } 225 | 226 | public static void DelayRemoveActiveEvent(ActiveEvent eventToRemove, float delay = 1) 227 | { 228 | if (!ValidateManager()) 229 | { 230 | return; 231 | } 232 | 233 | Instance.delayedEventsToRemove.Add(new ActiveEventRemovalTimestamp(Time.time + delay, eventToRemove)); 234 | } 235 | 236 | public static IEnumerator RemoveActiveEventCoroutine(ActiveEvent eventToRemove, float delay) 237 | { 238 | yield return new WaitForSeconds(delay); 239 | AudioManager.RemoveActiveEvent(eventToRemove); 240 | } 241 | 242 | #endregion 243 | 244 | #region Private Functions 245 | 246 | private void Update() 247 | { 248 | // First check and remove delayed events 249 | if (lastTime > Time.time) 250 | { 251 | // Time.time has rolled over, lets kludge it and remove all (This should be rare) 252 | Debug.LogWarning("Time Rollover, clearing all delayed events"); 253 | foreach (var eventToRemove in delayedEventsToRemove) 254 | { 255 | RemoveActiveEvent(eventToRemove.ActiveEvent); 256 | } 257 | 258 | delayedEventsToRemove.Clear(); 259 | } 260 | else 261 | { 262 | for (int i = delayedEventsToRemove.Count - 1; i >= 0; i--) 263 | { 264 | if (delayedEventsToRemove[i].RemovalTime < Time.time) 265 | { 266 | RemoveActiveEvent(delayedEventsToRemove[i].ActiveEvent); 267 | delayedEventsToRemove.RemoveAt(i); 268 | } 269 | } 270 | } 271 | lastTime = Time.time; 272 | 273 | for (int i = 0; i < ActiveEvents.Count; i++) 274 | { 275 | ActiveEvent tempEvent = ActiveEvents[i]; 276 | if (tempEvent != null && tempEvent.sources.Count != 0) 277 | { 278 | tempEvent.Update(); 279 | } 280 | } 281 | } 282 | 283 | /// 284 | /// Instantiate a new GameObject and add the AudioManager component 285 | /// 286 | private static void CreateInstance() 287 | { 288 | if (Instance != null || !AllowCreateInstance) 289 | { 290 | return; 291 | } 292 | 293 | CurrentLanguage = 0; 294 | ActiveEvents = new List(); 295 | GameObject instanceObject = new GameObject("AudioManager"); 296 | Instance = instanceObject.AddComponent(); 297 | DontDestroyOnLoad(instanceObject); 298 | CreateSources(); 299 | Application.quitting += HandleQuitting; 300 | } 301 | 302 | /// 303 | /// On shutdown we cannot create an instance 304 | /// 305 | private static void HandleQuitting() 306 | { 307 | AllowCreateInstance = false; 308 | } 309 | 310 | /// 311 | /// Create the pool of AudioSources 312 | /// 313 | private static void CreateSources() 314 | { 315 | for (int i = 0; i < DefaultSourcesCount; i++) 316 | { 317 | GameObject sourceGO = new GameObject("AudioSource" + i); 318 | sourceGO.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f); 319 | sourceGO.transform.SetParent(Instance.transform); 320 | AudioSource tempSource = sourceGO.AddComponent(); 321 | tempSource.playOnAwake = false; 322 | SourcePool.Add(tempSource); 323 | AvailableSources.Add(tempSource); 324 | #if UNITY_EDITOR 325 | TextMesh newText = sourceGO.AddComponent(); 326 | newText.characterSize = 0.2f; 327 | #endif 328 | } 329 | } 330 | 331 | private static void ClearSourceText() 332 | { 333 | for (int i = 0; i < AvailableSources.Count; i++) 334 | { 335 | TextMesh tempText = AvailableSources[i].GetComponent(); 336 | if (tempText != null) 337 | { 338 | tempText.text = string.Empty; 339 | } 340 | } 341 | } 342 | 343 | /// 344 | /// Get the number of active instances of an AudioEvent 345 | /// 346 | /// The event to query the number of active instances of 347 | /// The number of active instances of the specified AudioEvent 348 | private static int CountActiveInstances(AudioEvent audioEvent) 349 | { 350 | int tempCount = 0; 351 | 352 | for (int i = 0; i < ActiveEvents.Count; i++) 353 | { 354 | if (ActiveEvents[i].rootEvent == audioEvent) 355 | { 356 | tempCount++; 357 | } 358 | } 359 | 360 | return tempCount; 361 | } 362 | 363 | /// 364 | /// Call an immediate stop on all active audio events of a particular group 365 | /// 366 | /// The group number to stop 367 | private static void StopGroupInstances(int groupNum) 368 | { 369 | for (int i = 0; i < ActiveEvents.Count; i++) 370 | { 371 | ActiveEvent tempEvent = ActiveEvents[i]; 372 | if (tempEvent.rootEvent.Group == groupNum) 373 | { 374 | Debug.LogFormat("Stopping: {0}", tempEvent.name); 375 | tempEvent.StopImmediate(); 376 | } 377 | } 378 | } 379 | 380 | /// 381 | /// Look for an existing AudioSource component that is not currently playing 382 | /// 383 | /// The GameObject the AudioSource needs to be attached to 384 | /// An AudioSource reference if one exists, otherwise null 385 | public static AudioSource GetUnusedSource() 386 | { 387 | ClearNullAudioSources(); 388 | 389 | if (AvailableSources.Count > 0) 390 | { 391 | AudioSource tempSource = AvailableSources[0]; 392 | AvailableSources.Remove(tempSource); 393 | return tempSource; 394 | } 395 | else 396 | { 397 | return null; 398 | } 399 | } 400 | 401 | /// 402 | /// Remove any references to AudioSource components that no longer exist 403 | /// 404 | private static void ClearNullAudioSources() 405 | { 406 | for (int i = AvailableSources.Count - 1; i >= 0; i--) 407 | { 408 | AudioSource tempSource = AvailableSources[i]; 409 | if (tempSource == null) 410 | { 411 | AvailableSources.RemoveAt(i); 412 | } 413 | } 414 | } 415 | 416 | /// 417 | /// Make sure that the AudioManager has all of the required components 418 | /// 419 | /// Whether there is a valid AudioManager instance 420 | public static bool ValidateManager() 421 | { 422 | if (Instance == null) 423 | { 424 | CreateInstance(); 425 | } 426 | 427 | return Instance != null; 428 | } 429 | 430 | private static bool ValidateEvent(AudioEvent eventToPlay) 431 | { 432 | if (eventToPlay == null) 433 | { 434 | return false; 435 | } 436 | 437 | if (!eventToPlay.ValidateAudioFiles()) 438 | { 439 | return false; 440 | } 441 | 442 | if (eventToPlay.InstanceLimit > 0 && CountActiveInstances(eventToPlay) >= eventToPlay.InstanceLimit) 443 | { 444 | return false; 445 | } 446 | 447 | if (eventToPlay.Group != 0) 448 | { 449 | StopGroupInstances(eventToPlay.Group); 450 | } 451 | 452 | return true; 453 | } 454 | 455 | #endregion 456 | 457 | #region Editor 458 | 459 | /// 460 | /// Mute all ActiveEvents that are not soloed 461 | /// 462 | public static void ApplyActiveSolos() 463 | { 464 | ValidateManager(); 465 | 466 | bool soloActive = false; 467 | for (int i = 0; i < ActiveEvents.Count; i++) 468 | { 469 | if (ActiveEvents[i].Soloed) 470 | { 471 | soloActive = true; 472 | } 473 | } 474 | 475 | if (soloActive) 476 | { 477 | for (int i = 0; i < ActiveEvents.Count; i++) 478 | { 479 | ActiveEvents[i].ApplySolo(); 480 | } 481 | } 482 | else 483 | { 484 | ClearActiveSolos(); 485 | } 486 | } 487 | 488 | /// 489 | /// Unmute all events 490 | /// 491 | public static void ClearActiveSolos() 492 | { 493 | ValidateManager(); 494 | 495 | for (int i = 0; i < ActiveEvents.Count; i++) 496 | { 497 | ActiveEvents[i].ClearSolo(); 498 | } 499 | } 500 | 501 | #endregion 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /Runtime/AudioManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: afb2e910c92bff445965058697033f1a 3 | timeCreated: 1518568697 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | /// 12 | /// A generic container for audio assets and containers in an AudioEvent 13 | /// 14 | [System.Serializable] 15 | public class AudioNode : ScriptableObject 16 | { 17 | /// 18 | /// The visual size of the node in the graph 19 | /// 20 | [SerializeField] 21 | protected Rect nodeRect = new Rect(0, 0, 200, 100); 22 | /// 23 | /// The left connector for the node that can be connected to an AudioNodeOutput 24 | /// 25 | [SerializeField] 26 | protected AudioNodeInput input; 27 | /// 28 | /// The right connector for the node that can be connected to an AudioNodeInput 29 | /// 30 | [SerializeField, HideInInspector] 31 | protected AudioNodeOutput output; 32 | 33 | /// 34 | /// The image used for node input and output connectors 35 | /// 36 | protected static Texture2D ConnectorTexture; 37 | /// 38 | /// The minimum possible value for an AudioSource's volume property 39 | /// 40 | protected const float Volume_Min = 0; 41 | /// 42 | /// The maximum possible value for an AudioSource's volume property 43 | /// 44 | protected const float Volume_Max = 1; 45 | /// 46 | /// The minimum possible value for an AudioSource's pitch property 47 | /// 48 | protected const float Pitch_Min = 0.01f; 49 | /// 50 | /// The maximum possible value for an AudioSource's pitch property 51 | /// 52 | protected const float Pitch_Max = 3; 53 | 54 | /// 55 | /// Base function for node functionality when the AudioEvent is played 56 | /// 57 | /// The existing runtime instance of an event 58 | public virtual void ProcessNode(ActiveEvent activeEvent) 59 | { 60 | return; 61 | } 62 | 63 | /// 64 | /// Get the connected node of index nodeNum and process it on the ActiveEvent 65 | /// 66 | /// The index of the connected node to process 67 | /// The existing runtime instance of an AudioEvent 68 | protected void ProcessConnectedNode(int nodeNum, ActiveEvent activeEvent) 69 | { 70 | if (this.input == null) 71 | { 72 | Debug.LogWarningFormat("{0} does not have an input on node {1}", activeEvent, this.name); 73 | return; 74 | } 75 | 76 | if (nodeNum >= this.input.ConnectedNodes.Length) 77 | { 78 | Debug.LogWarningFormat("{0} tried to access invalid connected node {1}", this.name, nodeNum); 79 | return; 80 | } 81 | 82 | this.input.ConnectedNodes[nodeNum].ParentNode.ProcessNode(activeEvent); 83 | } 84 | 85 | /// 86 | /// Reset runtime properties associated with the node 87 | /// 88 | public virtual void Reset() 89 | { 90 | 91 | } 92 | 93 | #if UNITY_EDITOR 94 | 95 | /// 96 | /// EDITOR: Public accessor for the visual size of the node in the graph 97 | /// 98 | public Rect NodeRect 99 | { 100 | get { return this.nodeRect; } 101 | set { this.nodeRect = value; } 102 | } 103 | 104 | /// 105 | /// EDITOR: Public accessor for the left connector of the node 106 | /// 107 | public AudioNodeInput Input 108 | { 109 | get { return this.input; } 110 | } 111 | 112 | /// 113 | /// EDITOR: Public accessor for the right connector of the node 114 | /// 115 | public AudioNodeOutput Output 116 | { 117 | get { return this.output; } 118 | } 119 | 120 | /// 121 | /// EDITOR: Initialize required properties of the node when it is first created 122 | /// 123 | /// 124 | public virtual void InitializeNode(Vector2 position) 125 | { 126 | this.name = "Blank Node"; 127 | this.nodeRect.position = position; 128 | } 129 | 130 | /// 131 | /// EDITOR: Delete input and output connectors 132 | /// 133 | public virtual void DeleteConnections() 134 | { 135 | if (this.input != null) 136 | { 137 | ScriptableObject.DestroyImmediate(this.input, true); 138 | } 139 | if (this.output != null) 140 | { 141 | ScriptableObject.DestroyImmediate(this.output, true); 142 | } 143 | } 144 | 145 | /// 146 | /// EDITOR: Draw draggable GUI window in the graph 147 | /// 148 | /// 149 | public virtual void DrawNode(int id) 150 | { 151 | this.nodeRect = GUI.Window(id, this.nodeRect, DrawWindow, this.name); 152 | DrawInput(); 153 | DrawOutput(); 154 | } 155 | 156 | /// 157 | /// EDITOR: Set the position of the node in the graph 158 | /// 159 | /// 160 | public void SetPosition(Vector2 newPosition) 161 | { 162 | this.nodeRect.position = newPosition; 163 | } 164 | 165 | public void MoveBy(Vector2 offset) 166 | { 167 | nodeRect.position -= offset; 168 | if (this.input != null) 169 | { 170 | AudioNodeOutput[] outputs = this.input.ConnectedNodes; 171 | for (int i = 0; i < outputs.Length; i++) 172 | { 173 | outputs[i].ParentNode.MoveBy(offset); 174 | } 175 | } 176 | } 177 | 178 | /// 179 | /// EDITOR: Add a left connector to the node when it is first created 180 | /// 181 | /// 182 | protected void AddInput(bool singleConnection = false) 183 | { 184 | this.input = ScriptableObject.CreateInstance(); 185 | AssetDatabase.AddObjectToAsset(this.input, this); 186 | this.input.name = this.name + "Input"; 187 | this.input.ParentNode = this; 188 | this.input.SetSingleConnection(singleConnection); 189 | } 190 | 191 | /// 192 | /// EDITOR: Add a right connector to the node when it is first created 193 | /// 194 | protected void AddOutput() 195 | { 196 | this.output = ScriptableObject.CreateInstance(); 197 | AssetDatabase.AddObjectToAsset(this.output, this); 198 | this.output.name = this.name + "Output"; 199 | this.output.ParentNode = this; 200 | } 201 | 202 | /// 203 | /// EDITOR: Draw the properties of the node in the graph and the drag window 204 | /// 205 | /// 206 | protected void DrawWindow(int id) 207 | { 208 | DrawProperties(); 209 | GUI.DragWindow(); 210 | } 211 | 212 | /// 213 | /// EDITOR: Draw associated properties in the node draggable window 214 | /// 215 | protected virtual void DrawProperties() {} 216 | 217 | /// 218 | /// EDITOR: Draw the left connector on the node in the graph 219 | /// 220 | protected virtual void DrawInput() 221 | { 222 | if (this.input == null) 223 | { 224 | return; 225 | } 226 | 227 | Vector2 tempPos = new Vector2(this.nodeRect.x, this.nodeRect.y); 228 | tempPos.x -= this.input.Window.width; 229 | tempPos.y += (this.nodeRect.height / 2) - 5; 230 | this.input.Window.position = tempPos; 231 | 232 | if (ConnectorTexture == null) 233 | { 234 | ConnectorTexture = EditorGUIUtility.Load("icons/animationkeyframe.png") as Texture2D; 235 | } 236 | GUI.DrawTexture(this.input.Window, ConnectorTexture); 237 | 238 | for (int i = 0; i < this.input.ConnectedNodes.Length; i++) 239 | { 240 | AudioNodeOutput tempOutput = this.input.ConnectedNodes[i]; 241 | DrawCurve(tempOutput.Center, this.input.Center); 242 | } 243 | } 244 | 245 | /// 246 | /// EDITOR: Draw the right connector on the node in the graph 247 | /// 248 | protected virtual void DrawOutput() 249 | { 250 | if (this.output == null) 251 | { 252 | return; 253 | } 254 | 255 | Vector2 tempPos = new Vector2(this.nodeRect.x, this.nodeRect.y); 256 | tempPos.x += this.nodeRect.width; 257 | tempPos.y += (this.nodeRect.height / 2) - 10; 258 | this.output.Window.position = tempPos; 259 | 260 | if (ConnectorTexture == null) 261 | { 262 | ConnectorTexture = EditorGUIUtility.Load("icons/animationkeyframe.png") as Texture2D; 263 | } 264 | GUI.DrawTexture(this.output.Window, ConnectorTexture); 265 | } 266 | 267 | /// 268 | /// EDITOR: Draw the line connecting two nodes using a Bezier curve 269 | /// 270 | /// Position of the input node 271 | /// Position of the output node 272 | public static void DrawCurve(Vector2 start, Vector2 end) 273 | { 274 | Vector3 startPosition = new Vector3(start.x, start.y); 275 | Vector3 endPosition = new Vector3(end.x, end.y); 276 | Vector3 startTangent = startPosition + (Vector3.right * 50); 277 | Vector3 endTangent = endPosition + (Vector3.left * 50); 278 | Handles.DrawBezier(startPosition, endPosition, startTangent, endTangent, Color.white, null, 2); 279 | } 280 | 281 | #endif 282 | } 283 | } -------------------------------------------------------------------------------- /Runtime/AudioNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4b09b91dd89f14442851b24ee6a4faef 3 | timeCreated: 1518568382 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioNodeInput.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | using System.Collections.Generic; 6 | #if UNITY_EDITOR 7 | using UnityEditor; 8 | #endif 9 | 10 | namespace Microsoft.MixedReality.Toolkit.Audio 11 | { 12 | /// 13 | /// The input connector for an AudioNode 14 | /// 15 | public class AudioNodeInput : ScriptableObject 16 | { 17 | /// 18 | /// Perimeter of the connector object in the graph 19 | /// 20 | public Rect Window = new Rect(0, 0, ConnectorSize, ConnectorSize); 21 | /// 22 | /// The node that this connector is an input for 23 | /// 24 | [SerializeField] 25 | private AudioNode parentNode = null; 26 | /// 27 | /// All of the outputs that have connections to this input 28 | /// 29 | [SerializeField] 30 | private AudioNodeOutput[] connectedNodes = new AudioNodeOutput[0]; 31 | 32 | #if UNITY_EDITOR 33 | 34 | /// 35 | /// Whether the connector accepts more than one output to connect to it 36 | /// 37 | [SerializeField, HideInInspector] 38 | private bool forceSingleConnection = false; 39 | 40 | #endif 41 | 42 | /// 43 | /// The size in pixels for the connector in the graph 44 | /// 45 | private const float ConnectorSize = 20; 46 | 47 | /// 48 | /// EDTIOR: The position of the center of the connector's Rect 49 | /// 50 | public Vector2 Center 51 | { 52 | get 53 | { 54 | Vector2 tempPos = this.Window.position; 55 | tempPos.x += ConnectorSize / 2; 56 | tempPos.y += ConnectorSize / 2; 57 | return tempPos; 58 | } 59 | } 60 | 61 | /// 62 | /// Public accessor for the outputs connected to this input 63 | /// 64 | public AudioNodeOutput[] ConnectedNodes 65 | { 66 | get { return this.connectedNodes; } 67 | } 68 | 69 | /// 70 | /// Public accessor for the node that this connector is an input for 71 | /// 72 | public AudioNode ParentNode 73 | { 74 | get { return this.parentNode; } 75 | set { this.parentNode = value; } 76 | } 77 | 78 | #if UNITY_EDITOR 79 | 80 | /// 81 | /// EDITOR: Toggle whether this input can accept multiple connections or if a new connection will overwrite the previous one 82 | /// 83 | /// 84 | public void SetSingleConnection(bool toggle) 85 | { 86 | this.forceSingleConnection = toggle; 87 | } 88 | 89 | /// 90 | /// EDITOR: Connect a new output to this input 91 | /// 92 | /// The new output to connect 93 | public void AddConnection(AudioNodeOutput newOutput) 94 | { 95 | if (this.forceSingleConnection) 96 | { 97 | AudioNodeOutput[] singleOutput = new AudioNodeOutput[1]; 98 | singleOutput[0] = newOutput; 99 | this.connectedNodes = singleOutput; 100 | return; 101 | } 102 | 103 | for (int i = 0; i < this.connectedNodes.Length; i++) 104 | { 105 | if (this.connectedNodes[i] == newOutput) 106 | { 107 | return; 108 | } 109 | } 110 | 111 | AudioNodeOutput[] newOutputs = new AudioNodeOutput[this.connectedNodes.Length + 1]; 112 | this.connectedNodes.CopyTo(newOutputs, 0); 113 | newOutputs[newOutputs.Length - 1] = newOutput; 114 | this.connectedNodes = newOutputs; 115 | EditorUtility.SetDirty(this); 116 | } 117 | 118 | /// 119 | /// EDITOR: Sort the inputs in descending vertical order in the graph 120 | /// 121 | public void SortConnections() 122 | { 123 | List updatedNodes = new List(); 124 | 125 | while (updatedNodes.Count < this.connectedNodes.Length) 126 | { 127 | AudioNode nextNode = this.connectedNodes[0].ParentNode; 128 | for (int i = 0; i < this.connectedNodes.Length; i++) 129 | { 130 | AudioNode tempNode = this.connectedNodes[i].ParentNode; 131 | if (updatedNodes.Contains(nextNode.Output) || (tempNode.NodeRect.y < nextNode.NodeRect.y && !updatedNodes.Contains(tempNode.Output))) 132 | { 133 | nextNode = tempNode; 134 | } 135 | } 136 | updatedNodes.Add(nextNode.Output); 137 | } 138 | 139 | this.connectedNodes = updatedNodes.ToArray(); 140 | } 141 | 142 | /// 143 | /// EDITOR: Clear an output connection 144 | /// 145 | /// Output to disconnect from this input 146 | public void RemoveConnection(AudioNodeOutput outputToDelete) 147 | { 148 | if (outputToDelete == null) 149 | { 150 | return; 151 | } 152 | 153 | List updatedNodes = new List(); 154 | 155 | for (int i = this.connectedNodes.Length - 1; i >= 0; i--) 156 | { 157 | AudioNodeOutput tempOutput = this.connectedNodes[i]; 158 | if (tempOutput != outputToDelete) 159 | { 160 | updatedNodes.Add(tempOutput); 161 | } 162 | } 163 | 164 | this.connectedNodes = updatedNodes.ToArray(); 165 | } 166 | 167 | /// 168 | /// EDITOR: Disconnect all output connections 169 | /// 170 | public void RemoveAllConnections() 171 | { 172 | this.connectedNodes = new AudioNodeOutput[0]; 173 | } 174 | 175 | #endif 176 | } 177 | } -------------------------------------------------------------------------------- /Runtime/AudioNodeInput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4a81b464ba9745e44be8820ad1c8cfd6 3 | timeCreated: 1518745778 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioNodeOutput.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | 6 | namespace Microsoft.MixedReality.Toolkit.Audio 7 | { 8 | /// 9 | /// The output connector for an AudioNode 10 | /// 11 | public class AudioNodeOutput : ScriptableObject 12 | { 13 | /// 14 | /// Perimeter of the connector object in the graph 15 | /// 16 | public Rect Window = new Rect(0, 0, ConnectorSize, ConnectorSize); 17 | /// 18 | /// The node that this connector is an output for 19 | /// 20 | [SerializeField] 21 | private AudioNode parentNode = null; 22 | 23 | /// 24 | /// EDTIOR: The position of the center of the connector's Rect 25 | /// 26 | public Vector2 Center 27 | { 28 | get 29 | { 30 | Vector2 tempPos = this.Window.position; 31 | tempPos.x += ConnectorSize / 2; 32 | tempPos.y += ConnectorSize / 2; 33 | return tempPos; 34 | } 35 | } 36 | 37 | /// 38 | /// The size in pixels for the connector in the graph 39 | /// 40 | private const float ConnectorSize = 20; 41 | 42 | /// 43 | /// Public accessor for the node that this connector is an output for 44 | /// 45 | public AudioNode ParentNode 46 | { 47 | get { return this.parentNode; } 48 | set { this.parentNode = value; } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Runtime/AudioNodeOutput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f45cf000cc6667c4e9ad0ade0cfc82a7 3 | timeCreated: 1518745743 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioNullFile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | #if UNITY_EDITOR 5 | using UnityEditor; 6 | #endif 7 | 8 | namespace Microsoft.MixedReality.Toolkit.Audio 9 | { 10 | public class AudioNullFile : AudioNode 11 | { 12 | #if UNITY_EDITOR 13 | private const float NodeHeight = 50; 14 | 15 | public override void InitializeNode(Vector2 position) 16 | { 17 | this.name = "Null File"; 18 | this.nodeRect.position = position; 19 | this.nodeRect.height = NodeHeight; 20 | AddOutput(); 21 | EditorUtility.SetDirty(this); 22 | } 23 | #endif 24 | } 25 | } -------------------------------------------------------------------------------- /Runtime/AudioNullFile.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7e24d68fe03363944b170bd2b4af4a21 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioOutput.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using UnityEngine; 6 | using UnityEngine.Audio; 7 | #if UNITY_EDITOR 8 | using UnityEditor; 9 | #endif 10 | 11 | namespace Microsoft.MixedReality.Toolkit.Audio 12 | { 13 | /// 14 | /// The final node in an audio event 15 | /// 16 | public class AudioOutput : AudioNode 17 | { 18 | /// 19 | /// The audio bus to route this event to 20 | /// 21 | [SerializeField] 22 | public AudioMixerGroup mixerGroup = null; 23 | /// 24 | /// The low end of the random volume assigned when playing the event 25 | /// 26 | [Range(0, 1)] 27 | public float MinVolume = 1; 28 | /// 29 | /// The high end of the random volume assigned when playing the event 30 | /// 31 | [Range(0, 1)] 32 | public float MaxVolume = 1; 33 | /// 34 | /// The low end of the random pitch assigned when playing the event 35 | /// 36 | [Range(0.01f, 3)] 37 | public float MinPitch = 1; 38 | /// 39 | /// The high end of the random pitch assigned when playing the event 40 | /// 41 | [Range(0.01f, 3)] 42 | public float MaxPitch = 1; 43 | /// 44 | /// Whether to make the sound seamlessly loop 45 | /// 46 | [SerializeField] 47 | public bool loop = false; 48 | /// 49 | /// Amount of spatialization applied to the AudioSource 50 | /// 51 | [SerializeField] 52 | public float spatialBlend = 0; 53 | /// 54 | /// Whether to use the spatializer assigned in the project's audio settings 55 | /// 56 | [SerializeField] 57 | public bool HRTF = false; 58 | /// 59 | /// The distance beyond which the sound can no longer be heard 60 | /// 61 | [SerializeField] 62 | public float MaxDistance = 10; 63 | /// 64 | /// The response curve for how loud the sound will be at different distances 65 | /// 66 | [SerializeField] 67 | public AnimationCurve attenuationCurve = new AnimationCurve(); 68 | /// 69 | /// The amount of doppler effect applied to the sound when moving relative to the listener 70 | /// 71 | [SerializeField] 72 | public float dopplerLevel = 1; 73 | /// 74 | /// Slider for the amount of reverb applied to the event from the reverb zone 75 | /// 76 | [SerializeField, Range(0,1.1f)] 77 | public float ReverbZoneMix = 1; 78 | /// 79 | /// Sets the spread angle (in degrees) of a 3d stereo or multichannel sound in speaker space. 80 | /// 81 | [SerializeField, Range(0, 360)] 82 | public float Spread = 0; 83 | 84 | /// 85 | /// The width in pixels for the node's window in the graph 86 | /// 87 | private const float NodeWidth = 300; 88 | /// 89 | /// The height in pixels for the node's window in the graph 90 | /// 91 | private const float NodeHeight = 250; 92 | 93 | /// 94 | /// Apply all of the properties to the ActiveEvent and start processing the rest of the event's nodes 95 | /// 96 | /// 97 | public override void ProcessNode(ActiveEvent activeEvent) 98 | { 99 | if (this.input.ConnectedNodes == null || this.input.ConnectedNodes.Length == 0) 100 | { 101 | Debug.LogWarningFormat("No connected nodes for {0}", this.name); 102 | return; 103 | } 104 | 105 | activeEvent.SetVolume(Random.Range(this.MinVolume, this.MaxVolume)); 106 | activeEvent.SetPitch(Random.Range(this.MinPitch, this.MaxPitch)); 107 | 108 | ProcessConnectedNode(0, activeEvent); 109 | 110 | SetSourceProperties(activeEvent.sources); 111 | } 112 | 113 | private void SetSourceProperties(List sources) 114 | { 115 | for (int i = 0; i < sources.Count; i++) 116 | { 117 | AudioSource eventSource = sources[i].source; 118 | eventSource.outputAudioMixerGroup = this.mixerGroup; 119 | eventSource.loop = this.loop; 120 | eventSource.spatialBlend = this.spatialBlend; 121 | if (this.spatialBlend > 0) 122 | { 123 | eventSource.spatialize = this.HRTF; 124 | eventSource.maxDistance = this.MaxDistance; 125 | eventSource.rolloffMode = AudioRolloffMode.Custom; 126 | eventSource.SetCustomCurve(AudioSourceCurveType.CustomRolloff, this.attenuationCurve); 127 | eventSource.dopplerLevel = this.dopplerLevel; 128 | eventSource.reverbZoneMix = this.ReverbZoneMix; 129 | if (this.ReverbZoneMix == 0) 130 | { 131 | eventSource.bypassReverbZones = true; 132 | } 133 | eventSource.spread = this.Spread; 134 | } 135 | } 136 | } 137 | 138 | #if UNITY_EDITOR 139 | 140 | /// 141 | /// EDITOR: Initialize variables for output settings 142 | /// 143 | /// The position of the node window in the graph 144 | public override void InitializeNode(Vector2 position) 145 | { 146 | this.name = "Output"; 147 | this.nodeRect.position = position; 148 | this.nodeRect.width = NodeWidth; 149 | this.nodeRect.height = NodeHeight; 150 | AddInput(); 151 | } 152 | 153 | /// 154 | /// EDITOR: Draw the node's properties in the node window in the graph 155 | /// 156 | protected override void DrawProperties() 157 | { 158 | this.mixerGroup = EditorGUILayout.ObjectField("Mixer Group", this.mixerGroup, typeof(AudioMixerGroup), false) as AudioMixerGroup; 159 | EditorGUILayout.MinMaxSlider("Volume", ref this.MinVolume, ref this.MaxVolume, Volume_Min, Volume_Max); 160 | EditorGUILayout.MinMaxSlider("Pitch", ref this.MinPitch, ref this.MaxPitch, Pitch_Min, Pitch_Max); 161 | this.loop = EditorGUILayout.Toggle("Loop", this.loop); 162 | this.spatialBlend = EditorGUILayout.Slider("Spatial Blend", this.spatialBlend, 0, 1); 163 | 164 | EditorGUI.BeginDisabledGroup(this.spatialBlend == 0); 165 | this.HRTF = EditorGUILayout.Toggle("HRTF", this.HRTF); 166 | this.MaxDistance = EditorGUILayout.FloatField("Max Distance", this.MaxDistance); 167 | this.attenuationCurve = EditorGUILayout.CurveField("Attenuation", this.attenuationCurve); 168 | this.dopplerLevel = EditorGUILayout.FloatField("Doppler Level", this.dopplerLevel); 169 | this.ReverbZoneMix = EditorGUILayout.Slider("Reverb Zone Mix", this.ReverbZoneMix, 0, 1.1f); 170 | this.Spread = EditorGUILayout.Slider("Spread", this.Spread, 0, 360f); 171 | EditorGUI.EndDisabledGroup(); 172 | } 173 | 174 | #endif 175 | } 176 | } -------------------------------------------------------------------------------- /Runtime/AudioOutput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: feb3210b711af0f4ab76fc3bff926f11 3 | timeCreated: 1519263794 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioParameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | /// 12 | /// A runtime value that affects audio properties on an AudioEvent 13 | /// 14 | public class AudioParameter : ScriptableObject 15 | { 16 | /// 17 | /// The value the parameter will be set to until explicitly set 18 | /// 19 | [SerializeField] 20 | private float defaultValue = 0; 21 | /// 22 | /// Whether the value will be updated with the angle between the camera and emitter 23 | /// 24 | [SerializeField] 25 | private bool useGaze = false; 26 | 27 | /// 28 | /// Public accessor for whether the value is updated with the angle between the camera and emitter 29 | /// 30 | public bool UseGaze 31 | { 32 | get { return this.useGaze; } 33 | } 34 | 35 | /// 36 | /// The current value of the parameter, before being evaluated on the response curve 37 | /// 38 | public float CurrentValue { get; private set; } 39 | 40 | /// 41 | /// Set the initial value of the parameter 42 | /// 43 | public void InitializeParameter() 44 | { 45 | this.CurrentValue = this.defaultValue; 46 | } 47 | 48 | /// 49 | /// Set the value back to default 50 | /// 51 | public void ResetParameter() 52 | { 53 | this.CurrentValue = this.defaultValue; 54 | } 55 | 56 | /// 57 | /// Set a new value for the parameter to be evaluated on the response curve 58 | /// 59 | /// The value to be set as CurrentValue 60 | public void SetValue(float newValue) 61 | { 62 | if (this.useGaze || newValue == this.CurrentValue) 63 | { 64 | return; 65 | } 66 | 67 | this.CurrentValue = newValue; 68 | } 69 | 70 | #if UNITY_EDITOR 71 | 72 | /// 73 | /// EDITOR: Draw the properties for the parameter in the graph 74 | /// 75 | public void DrawParameterEditor() 76 | { 77 | this.name = EditorGUILayout.TextField("Name", this.name); 78 | this.defaultValue = EditorGUILayout.FloatField("Default Value", this.defaultValue); 79 | this.useGaze = EditorGUILayout.Toggle("Use Gaze", this.useGaze); 80 | } 81 | 82 | #endif 83 | } 84 | } -------------------------------------------------------------------------------- /Runtime/AudioParameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9e15a089a26487439ab7021a43362d2 3 | timeCreated: 1522106463 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioRandomSelector.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | 6 | namespace Microsoft.MixedReality.Toolkit.Audio 7 | { 8 | /// 9 | /// An AudioNode for randomly choosing one of its connected nodes 10 | /// 11 | public class AudioRandomSelector : AudioNode 12 | { 13 | /// 14 | /// Randomly select a connected node 15 | /// 16 | /// The existing runtime audio event 17 | public override void ProcessNode(ActiveEvent activeEvent) 18 | { 19 | if (this.input.ConnectedNodes == null || this.input.ConnectedNodes.Length == 0) 20 | { 21 | Debug.LogWarningFormat("No connected nodes for {0}", this.name); 22 | return; 23 | } 24 | 25 | int nodeNum = Random.Range(0, this.input.ConnectedNodes.Length); 26 | 27 | ProcessConnectedNode(nodeNum, activeEvent); 28 | } 29 | 30 | #if UNITY_EDITOR 31 | 32 | /// 33 | /// EDITOR: Set the initial values for the node's properties 34 | /// 35 | /// The position of the node on the graph 36 | public override void InitializeNode(Vector2 position) 37 | { 38 | this.name = "Random Selector"; 39 | this.nodeRect.height = 50; 40 | this.nodeRect.width = 150; 41 | this.nodeRect.position = position; 42 | AddInput(); 43 | AddOutput(); 44 | } 45 | 46 | #endif 47 | } 48 | } -------------------------------------------------------------------------------- /Runtime/AudioRandomSelector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3a466c9b2ddc9d041a615c9ed2fd6d99 3 | timeCreated: 1518568450 4 | licenseType: Pro 5 | MonoImporter: 6 | externalObjects: {} 7 | serializedVersion: 2 8 | defaultReferences: [] 9 | executionOrder: 0 10 | icon: {instanceID: 0} 11 | userData: 12 | assetBundleName: 13 | assetBundleVariant: 14 | -------------------------------------------------------------------------------- /Runtime/AudioSequenceSelector.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | 6 | namespace Microsoft.MixedReality.Toolkit.Audio 7 | { 8 | /// 9 | /// Iterates over each of its connections when the event is played 10 | /// 11 | public class AudioSequenceSelector : AudioNode 12 | { 13 | /// 14 | /// The index of the connected node that will be chosen next time the event is played 15 | /// 16 | private int currentNode = 0; 17 | 18 | /// 19 | /// Process the node on the current index and increase the index for next playback 20 | /// 21 | /// The existing runtime event to set properties on 22 | public override void ProcessNode(ActiveEvent activeEvent) 23 | { 24 | ProcessConnectedNode(this.currentNode, activeEvent); 25 | 26 | this.currentNode++; 27 | 28 | if (this.currentNode >= this.input.ConnectedNodes.Length) 29 | { 30 | this.currentNode = 0; 31 | } 32 | } 33 | 34 | /// 35 | /// Reset the current node index back to 0 36 | /// 37 | public override void Reset() 38 | { 39 | this.currentNode = 0; 40 | } 41 | 42 | #if UNITY_EDITOR 43 | 44 | /// 45 | /// EDITOR: Set the initial values for the node's properties 46 | /// 47 | /// The position of the node's window in the graph 48 | public override void InitializeNode(Vector2 position) 49 | { 50 | this.name = "Sequence Selector"; 51 | this.nodeRect.height = 50; 52 | this.nodeRect.width = 150; 53 | this.nodeRect.position = position; 54 | AddInput(); 55 | AddOutput(); 56 | } 57 | 58 | #endif 59 | } 60 | } -------------------------------------------------------------------------------- /Runtime/AudioSequenceSelector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 50ac781d8cb24b44faf72b9efa2811f2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioSnapshotTransition.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.MixedReality.Toolkit.Audio; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using UnityEngine; 8 | using UnityEngine.Audio; 9 | 10 | #if UNITY_EDITOR 11 | using UnityEditor; 12 | #endif 13 | 14 | namespace Microsoft.MixedReality.Toolkit.Audio 15 | { 16 | public class AudioSnapshotTransition : AudioNode 17 | { 18 | [SerializeField] 19 | private AudioMixerSnapshot snapshot; 20 | [SerializeField] 21 | private float transitionTime = 0f; 22 | [SerializeField] 23 | private bool useParameter = false; 24 | [SerializeField] 25 | private AudioParameter parameter = null; 26 | [SerializeField] 27 | private AnimationCurve responseCurve = null; 28 | 29 | /// 30 | /// Play the snapshot transition on the mixer 31 | /// 32 | /// The existing runtime audio event 33 | public override void ProcessNode(ActiveEvent activeEvent) 34 | { 35 | if (this.snapshot == null) 36 | { 37 | Debug.LogWarningFormat("No snapshot in transition trigger {0}", this.name); 38 | return; 39 | } 40 | 41 | activeEvent.hasSnapshotTransition = true; 42 | float effectiveTransitionTime = 0f; 43 | 44 | if (this.useParameter && this.parameter != null && this.responseCurve != null) 45 | { 46 | effectiveTransitionTime = this.responseCurve.Evaluate(this.parameter.CurrentValue); 47 | } 48 | else 49 | { 50 | effectiveTransitionTime = this.transitionTime; 51 | } 52 | 53 | this.snapshot.TransitionTo(effectiveTransitionTime); 54 | AudioManager.DelayRemoveActiveEvent(activeEvent, effectiveTransitionTime); 55 | 56 | if (this.input.ConnectedNodes != null && this.input.ConnectedNodes.Length != 0) 57 | { 58 | int nodeNum = Random.Range(0, this.input.ConnectedNodes.Length); 59 | ProcessConnectedNode(nodeNum, activeEvent); 60 | } 61 | } 62 | 63 | #if UNITY_EDITOR 64 | 65 | /// 66 | /// EDITOR: Set the initial values for the node's properties 67 | /// 68 | /// The position of the node on the graph 69 | public override void InitializeNode(Vector2 position) 70 | { 71 | this.name = "Audio Snapshot Transition"; 72 | this.nodeRect.height = 170; 73 | this.nodeRect.width = 300; 74 | this.nodeRect.position = position; 75 | AddInput(); 76 | AddOutput(); 77 | } 78 | 79 | protected override void DrawProperties() 80 | { 81 | this.snapshot = EditorGUILayout.ObjectField("Snapshot", this.snapshot, typeof(AudioMixerSnapshot), false) as AudioMixerSnapshot; 82 | if (this.snapshot != null) 83 | { 84 | this.name = this.snapshot.name; 85 | } 86 | EditorGUILayout.BeginToggleGroup("Timer", !this.useParameter); 87 | this.transitionTime = EditorGUILayout.FloatField("Transition Time", this.transitionTime); 88 | EditorGUILayout.EndToggleGroup(); 89 | this.useParameter = EditorGUILayout.Toggle("Use Parameter", this.useParameter); 90 | EditorGUILayout.BeginToggleGroup("Parameter", this.useParameter); 91 | this.parameter = EditorGUILayout.ObjectField("Parameter", this.parameter, typeof(AudioParameter), false) as AudioParameter; 92 | this.responseCurve = EditorGUILayout.CurveField("Response Curve", this.responseCurve); 93 | EditorGUILayout.EndToggleGroup(); 94 | } 95 | 96 | #endif 97 | } 98 | } -------------------------------------------------------------------------------- /Runtime/AudioSnapshotTransition.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7e01e2a5dd8aa194ca2f58147b544bb3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioSwitch.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | #if UNITY_EDITOR 3 | using UnityEditor; 4 | #endif 5 | 6 | namespace Microsoft.MixedReality.Toolkit.Audio 7 | { 8 | public class AudioSwitch : ScriptableObject 9 | { 10 | private int defaultValue = 0; 11 | public int CurrentValue { get; private set; } 12 | 13 | public void InitializeSwitch() 14 | { 15 | this.CurrentValue = this.defaultValue; 16 | } 17 | 18 | public void ResetSwitch() 19 | { 20 | this.CurrentValue = this.defaultValue; 21 | } 22 | 23 | public void SetValue(int newValue) 24 | { 25 | if (newValue == this.CurrentValue) 26 | { 27 | return; 28 | } 29 | 30 | this.CurrentValue = newValue; 31 | } 32 | 33 | #if UNITY_EDITOR 34 | 35 | public void DrawSwitchEditor() 36 | { 37 | this.name = EditorGUILayout.TextField("Name", this.name); 38 | this.defaultValue = EditorGUILayout.IntField("Default Value", this.defaultValue); 39 | } 40 | 41 | #endif 42 | } 43 | } -------------------------------------------------------------------------------- /Runtime/AudioSwitch.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5bd7ae48faf66684cab1a3b841575b15 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioSwitchSelector.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | /// 12 | /// An AudioNode for randomly choosing one of its connected nodes 13 | /// 14 | public class AudioSwitchSelector : AudioNode 15 | { 16 | [SerializeField] 17 | private AudioSwitch switchObject; 18 | 19 | /// 20 | /// Randomly select a connected node 21 | /// 22 | /// The existing runtime audio event 23 | public override void ProcessNode(ActiveEvent activeEvent) 24 | { 25 | if (this.input.ConnectedNodes == null || this.input.ConnectedNodes.Length == 0) 26 | { 27 | Debug.LogWarningFormat("No connected nodes for {0}", this.name); 28 | return; 29 | } 30 | 31 | int nodeNum = switchObject.CurrentValue; 32 | 33 | ProcessConnectedNode(nodeNum, activeEvent); 34 | } 35 | 36 | #if UNITY_EDITOR 37 | 38 | /// 39 | /// EDITOR: Set the initial values for the node's properties 40 | /// 41 | /// The position of the node on the graph 42 | public override void InitializeNode(Vector2 position) 43 | { 44 | this.name = "Switch Selector"; 45 | this.nodeRect.height = 50; 46 | this.nodeRect.width = 200; 47 | this.nodeRect.position = position; 48 | AddInput(); 49 | AddOutput(); 50 | } 51 | 52 | protected override void DrawProperties() 53 | { 54 | this.switchObject = EditorGUILayout.ObjectField(this.switchObject, typeof(AudioSwitch), false) as AudioSwitch; 55 | } 56 | 57 | #endif 58 | } 59 | } -------------------------------------------------------------------------------- /Runtime/AudioSwitchSelector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 461fdbeb904abd548bf4637b165c1e3b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AudioVoiceFile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Microsoft.MixedReality.Toolkit.Audio 10 | { 11 | /// 12 | /// An AudioNode containing a reference to a voice-over AudioClip 13 | /// 14 | public class AudioVoiceFile : AudioNode 15 | { 16 | /// 17 | /// The voice clip to be set on the AudioSource if this node is processed 18 | /// 19 | [SerializeField] 20 | private AudioClip file = null; 21 | 22 | /// 23 | /// The amount of volume change to apply if this node is processed 24 | /// 25 | [SerializeField, Range(-1, 1)] 26 | private float volumeOffset = 0; 27 | 28 | /// 29 | /// The amount of pitch change to apply if this node is processed 30 | /// 31 | [SerializeField, Range(-3, 3)] 32 | private float pitchOffset = 0; 33 | 34 | /// 35 | /// The language the voice file is spoken in 36 | /// 37 | [SerializeField] 38 | private int language = 0; 39 | 40 | /// 41 | /// The text associated with this voice clip, usually for subtitles 42 | /// 43 | [SerializeField] 44 | private string text = null; 45 | 46 | public int Language 47 | { 48 | get { return this.language; } 49 | } 50 | 51 | /// 52 | /// Apply all modifications to the ActiveEvent before it gets played 53 | /// 54 | /// The runtime event being prepared for playback 55 | public override void ProcessNode(ActiveEvent activeEvent) 56 | { 57 | if (file == null) 58 | { 59 | Debug.LogWarningFormat("Empty Voice File node in event {0}", activeEvent.name); 60 | } 61 | 62 | activeEvent.ModulateVolume(this.volumeOffset); 63 | activeEvent.ModulatePitch(this.pitchOffset); 64 | activeEvent.text = this.text; 65 | activeEvent.AddEventSource(this.file); 66 | } 67 | 68 | #if UNITY_EDITOR 69 | 70 | /// 71 | /// The width in pixels for the node's window in the graph 72 | /// 73 | private const float NodeWidth = 300; 74 | /// 75 | /// The height in pixels for the node's window in the graph 76 | /// 77 | private const float NodeHeight = 130; 78 | 79 | /// 80 | /// Public accessor for the voice clip in the node 81 | /// 82 | public AudioClip File 83 | { 84 | get { return this.file; } 85 | } 86 | 87 | /// 88 | /// EDITOR: Initialize the node's properties when it is first created 89 | /// 90 | /// The position of the new node in the graph 91 | public override void InitializeNode(Vector2 position) 92 | { 93 | this.name = "Voice File"; 94 | this.nodeRect.position = position; 95 | this.nodeRect.width = NodeWidth; 96 | this.nodeRect.height = NodeHeight; 97 | AddOutput(); 98 | EditorUtility.SetDirty(this); 99 | } 100 | 101 | /// 102 | /// EDITOR: Display the node's properties in the graph 103 | /// 104 | protected override void DrawProperties() 105 | { 106 | this.file = EditorGUILayout.ObjectField(this.file, typeof(AudioClip), false) as AudioClip; 107 | this.volumeOffset = EditorGUILayout.Slider("Volume Offset", this.volumeOffset, -1, 1); 108 | this.pitchOffset = EditorGUILayout.Slider("Pitch Offset", this.pitchOffset, -3, 3); 109 | this.text = EditorGUILayout.TextField("Text", this.text); 110 | this.language = EditorGUILayout.Popup("Language", this.language, AudioManager.Languages); 111 | } 112 | #endif 113 | } 114 | } -------------------------------------------------------------------------------- /Runtime/AudioVoiceFile.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 29fb2ae3271cf93499685adfec9ea9a3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/com.microsoft.audiomanagerforunity.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.microsoft.edt.lib.audiomanager" 3 | } 4 | -------------------------------------------------------------------------------- /Runtime/com.microsoft.audiomanagerforunity.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 60c6551998711ca4b9d346347b638d74 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.microsoft.audiomanagerforunity", 3 | "version": "0.3.0", 4 | "displayName": "Microsoft Audio Manager for Unity", 5 | "description": "Adds an audio event system for complex sound behaviors and controls.", 6 | "unity": "2019.4", 7 | "keywords": [ 8 | "audio", 9 | "sound" 10 | ], 11 | "author": { 12 | "name": "Microsoft" 13 | }, 14 | "hideInEditor": false 15 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b2cd7a1e2d354684ba2bf7424dddd22e 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------