├── .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 |
--------------------------------------------------------------------------------