A Tutorial Look at the Unity-FMOD Example Code

A Look at the FMOD Example Code:

Lately, I’ve been working through drafts of how to best write code modifying parameters in FMOD through Unity C# code, but, honestly, I’m way out of practice and I still don’t have the best answer yet. To re-familiarize myself, I wrote this up for some of the folks who have been emailing me about how to work with Parameters in code. I suspect that many of those questions have to do with not completely understanding some of the best coding practices with FMOD so I wrote this up using the included FMOD Tutorial file. It is a heavily commented, re-organized and restructured version of the FMOD StudioEventEmitter.cs document which is imported into your project as part of the Unity-FMOD Integration package found on the FMOD website. Please forgive any formatting errors – I tried to pretty-ify this as much as possible to ease in readability, but I’m painfully aware that other desktop monitors might display this differently. In any case, without further ado here is the FMOD Example Code, re-structured and described in detail:

	
	\*-------------------------------------------------------------------------------------*/
	 * This code was written by Christopher Prunotto, and is derived from example code	*
	 * packaged as part of the FMOD-Unity Integration package developed by Firelight	*
	 * Technologies for the purposes of integrating the FMOD Audio Engine into Unity 5.	*
	 * Please don't remove this header so that if someone else checks out this code,	*
	 * they know where it was found.							*
	 * 											*
	 * This particular document is being hosted at:						*
	 * http://blog.soudguychris.com/2015/09/08/fmod-example-code-tutorial			*
	 * This document is essentially the example code, but repackaged, restructured and 	*
	 * re-ordered with line-by-line comments to help a newcomer understand exactly what	*
	 * is happening when they use FMOD_StudioEventEmitter.cs file and how to properly 	*
	 * adapt it to their own games or apps.	It's very "pretty-ified" and so it's not	*
	 * the best idea to run this code in your game as written. Instead, use it as a		*
	 * guide, much like how you would use the actual FMOD Example scripts as examples.	*
	 * 											*
	 * Those example scripts can be found in the .unitypackage available for download	*
	 * on the FMOD website at http://www.FMOD.org/downloads and additional help and		*
	 * information can be found at http://www.FMOD.org/documentation			*
	 * 											*
	 * For other audio discussions, tutorials, and topics please visit my website at	*
	 * www.SoundGuyChris.com, or come and visit my blog at blog.SoundGuyChris.com!		*
	 * 											*
	 * Thanks for checking out my materials! I hope it helps you learn a bit about		*
	 * interactive audio. 🙂 As always, if you've got any questions or comments or just	*
	 * wanna talk shop (or hey, you know, hire me or something) email me at 		*
	 * Hello@chrisprunotto.com or hit me up on twitter @SoundGuyChris!			*
	\*-------------------------------------------------------------------------------------*/
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.InteropServices;
using FMOD.Studio;


public class FMOD_AudioTutorial : MonoBehaviour {

//----------------------------------------------------------------------------------------------*/
// Variable Declarations:
//----------------------------------------------------------------------------------------------*/

	FMOD.Studio.EventInstance evt; 
/* This is our FMOD Event which will handle most of our interfacing between FMOD and Unity  	*/

	public FMODAsset asset; 
/* A user-definable FMOD asset, which can be accessed through the Select Dialogue in the Unity 
   Property Inspector. Use this to select the event that this script will work on. 		*/

	public string path = ""; 				
/* Alternate way of defining the FMOD Event to be acted upon. The best way to use this is to 
   copy-paste the path directly from FMOD by right-clicking the event and using "Copy Path".	*/
	
	bool hasStarted = false; 				
/* This bool is set true once our event has started playing.					*/

	static bool isShuttingDown = false;
/* Are we shutting down the app? This is set by the script.					*/

	public bool startEventOnAwake = true; 	
/* A user-switchable toggle in the Unity Property Inspector to start the event as soon as 
   the object is awake.										*/

	Rigidbody cachedRigidBody;
/* A Rigidbody is for modelling 3D audio in a Scene. 2D Sounds don't explicitly need this.	*/
	
	[System.Serializable]
	public class Parameter
	{
		public string name;
		public float value;
	}
/* System Serializable allows you to create a custom class inside of another class. It 
   effectively lets you embed a sub-class inside the class, with properties in the Unity 
   Property Inspector. Here, we're creating a class called Parameter with the properties "name",
   which is a string, and "value", which is a float.						*/

//----------------------------------------------------------------------------------------------*/
// void Start Function: 
// When this MonoBehavior script starts, this function will be the first thing that runs (unless 
// you decide to create an Awake function or alter the Script Execution Order of your app. When 
// it starts, it finds out if the event is equal to null OR if it is not valid - if this happens, 
// then it will attempt to Cache the FMOD Event as defined in the CacheEventInstance function 
// described below. Take a quick peek down there if you're curious. Once it caches the event to 
// the "evt" variable, it gets the Rigidbody from the gameObject if there is one (RigidBodies 
// will be used in the Set3DAttributes function, also described below in order to determine 3D 
// Audio attributes). Once it has cached the Rigidbody, *IF* the StartEventOnAwake bool is
// set to true (by default, it is, but it can be toggled off in the Unity Properties Inspector, 
// or set to false above), then it will immediately run the StartEvent function, defined below.
//----------------------------------------------------------------------------------------------*/

	void Start()
	{
		if (evt == null || !evt.isValid())
		{
			CacheEventInstance();
		}
		
		cachedRigidBody = GetComponent<Rigidbody>();
		
		if (startEventOnAwake)
			StartEvent();
	}

//----------------------------------------------------------------------------------------------*/
// void Update Function: 
// This gets called on every single frame of the game, so make sure to keep it short and sweet. 
// In a nutshell, this particular Update function first checks if an event ("evt" is NOT null, 
// and IS valid, and if so, it will update the 3D Attributes of the event according to the 
// Update3DAttributes function, which is explained in more detail below, in order to calculate 
// distance/velocity of 3D gameObjects using the rigidbody they are attached to. This is a 
// fundamental part of creating 3D Audio. Without updating the 3D Attributes, the audio may 
// as well be a 2D sound. If the event check fails, (either the event is null, or it is for 
// some reason invalid, then we will set "evt" to simply be null and be done with it until we 
// assign it something new.
//----------------------------------------------------------------------------------------------*/

	void Update()
	{
		if (evt != null && evt.isValid ())
		{
			Update3DAttributes();
		}
		else
		{
			evt = null;
		}
	}

//----------------------------------------------------------------------------------------------*/
// FMOD.RESULT ERRCHECK Function:
// This is an FMOD provided Tool used for checking errors. It uses the FMOD_Soundsystem.cs file 
// to enumerate the codes found at http://www.fmod.org/docs/content/generated/FMOD_RESULT.html.
// The method being passed as an argument (for example, evt.start()) will only be performed if 
// the result of the methods FMOD.RESULT error code returns "OK". This, obviously, can only be 
// used on FMOD events.
// NOTE:
// It's important to know that you can't use ERRCHECK if "using FMOD.Studio;" isn't scoped 
// into this C# file.
//----------------------------------------------------------------------------------------------*/

	FMOD.RESULT ERRCHECK(FMOD.RESULT result)
	{
		FMOD.Studio.UnityUtil.ERRCHECK(result);
		return result;
	}

//----------------------------------------------------------------------------------------------*/
// Functions for Getting and Setting FMOD Information between FMOD Studio and Unity:
// The following three functions (CacheEventInstance, getParameter, and Update3DAttributes are 
// responsible for some of the core interfacing between the FMOD Audio Engine's data and the 
// data used in Unity.
//----------------------------------------------------------------------------------------------*/
//----------------------------------------------------------------------------------------------*/
//// void CacheEventInstance Function: 
//// This function is responsible for assigning an FMOD Audio Event to the FMOD Event 
//// Instance being handled so that it can be manipulated. If the asset is NOT null, then evt 
//// (our FMOD event instance) is set to be the ID of the FMOD Asset that the user can define 
//// in the Unity Properties Inspector. If it IS null, then evt will attempt to be set using 
//// whatever string is in the string variable "path", provided that "path" is not null or empty. 
//// If THAT doesn't work, then just log an FMOD Debug Error: No asset or path specified for the 
//// Event Emitter.
////---------------------------------------------------------------------------------------------*/

	void CacheEventInstance()
	{
		if (asset != null)
		{
			evt = FMOD_StudioSystem.instance.GetEvent(asset.id);
		}
		else if (!string.IsNullOrEmpty(path))
		{
			evt = FMOD_StudioSystem.instance.GetEvent(path);
		}
		else
		{
			FMOD.Studio.UnityUtil.LogError("No asset or path specified for Event Emitter");
		}
	}
	
////----------------------------------------------------------------------------------------------*/
//// ParameterInstance getParameter Function:
//// A ParameterInstance is comparable to an EventInstance such that it is an instance of the 
//// FMOD Parameter that our code will be operating upon. In this function, we pass the name of 
//// our parameter as a string into the function, and set param to null so that it can be 
//// ERRCHECK'ed, and then try to use the string "name" which was passed into the function, 
//// assign it to a ParameterInstance called "param", and then return it. This process of 
//// getting parameters is vitally important to using interactive audio. Once you have "gotten" 
//// your Parameter, it can then be manipulated by using other methods such as "setParameter" 
//// and the like.
////----------------------------------------------------------------------------------------------*/

	public FMOD.Studio.ParameterInstance getParameter(string name)
	{
		FMOD.Studio.ParameterInstance param = null;
		ERRCHECK(evt.getParameter(name, out param));
		
		return param;
	}

////----------------------------------------------------------------------------------------------*/
//// Update3DAttributes Function:
//// In the context of this C# file, Update3DAttributes() is being called at least once on every 
//// single frame when an object is running this script. Upon checking if a given event is not 
//// null and is valid, attributes are assigned to a new variable called attributes, and an 
//// internal FMOD function called to3DAttributes is run, using using FMOD_StudioSystem.cs, to 
//// create FMOD attributes which measure the orientation of the x,y,z coordinates and 
//// transform.position of the gameObject's rigidbody (as well as its velocity!) in order to 
//// calculate how 3D audio needs to be processed.
////----------------------------------------------------------------------------------------------*/

	void Update3DAttributes()
	{
		if (evt != null && evt.isValid ())
		{
			var attributes = FMOD.Studio.UnityUtil.to3DAttributes(gameObject, cachedRigidBody);
			ERRCHECK(evt.set3DAttributes(attributes));
		}
	}

//----------------------------------------------------------------------------------------------*/
// Utility Functions for Querying Audio States:
// The following two functions (HasFinished and getPlaybackState) are generally for determining 
// the playback status of a given Audio Event.
//----------------------------------------------------------------------------------------------*/
////--------------------------------------------------------------------------------------------*/
//// bool HasFinished Function:
//// Relatively self-explanatory. This utility function determines whether or not an event has 
//// finished playing or not. If "hasStarted" is false, return false. If the event either IS 
//// null, OR the event is invalid, return true. If, by this point, nothing has been returned 
//// (ie: the event is NOT null, and it IS a valid event, then simply compare the playback 
//// state to STOPPED and evaluate whether it is true or not. Note that this can possibly result 
//// in false positives if, for example, the playbackstate is set to paused. Since 
//// getPlayBackState for PAUSED would not be equal to STOPPED, then HasFinished would return 
//// true...though to be totally honest, I haven't tested this for myself yet.
////----------------------------------------------------------------------------------------------*/
	
	public bool HasFinished()
	{
		if (!hasStarted)
			return false;
		if (evt == null || !evt.isValid())
			return true;
		
		return getPlaybackState () == FMOD.Studio.PLAYBACK_STATE.STOPPED;
	}

////----------------------------------------------------------------------------------------------*/
//// PLAYBACK_STATE getPlaybackState Function:
//// Also relatively self-explanatory. This is used by the previous function especially often. It 
//// simply returns the FMOD Playback State of a given event by checking if the event is equal to 
//// null, OR if it is invalid in which case "STOPPED" is returned. If we haven't returned STOPPED 
//// yet, a variable called "state" is created with the temporary value of STOPPED. The event is 
//// then asked its internal PlaybackState which is then assigned to the variable "state", and is 
//// compared against "OK" and "state" is returned (that is, if the ERRCHECK returns OK, then we 
//// know "state" has been set properly and can be safely returned. If nothing so far has been 
//// returned to the program, then simply return STOPPED. 
////----------------------------------------------------------------------------------------------*/

	public FMOD.Studio.PLAYBACK_STATE getPlaybackState()
	{
		if (evt == null || !evt.isValid())
			return FMOD.Studio.PLAYBACK_STATE.STOPPED;
			
		FMOD.Studio.PLAYBACK_STATE state = PLAYBACK_STATE.STOPPED;

		if (ERRCHECK (evt.getPlaybackState(out state)) == FMOD.RESULT.OK)
			return state;
		
		return FMOD.Studio.PLAYBACK_STATE.STOPPED;
	}

//----------------------------------------------------------------------------------------------*/
// Functions for Starting Audio Events:
// The following two functions (StartEvent, and Play) are  related to actually starting a given 
// FMOD Event in (slightly different ways)
//----------------------------------------------------------------------------------------------*/
////--------------------------------------------------------------------------------------------*/
//// void StartEvent Function:
//// Relatively self-explanatory: if the event is equal to null or is not valid, then it will 
//// try to Cache the event as was done in the Start function. CacheEventInstance is described 
//// above in further detail. If the event is NOT null, and IS valid, then it will run 
//// Update3DAttributes to set the attributes of the Event by using the rigidbody in order to 
//// calculate its it's 3D position in the game space. Then, it runs the ERRCHECK function and 
//// actually plays the event if ERRCHECK returns OK. ERRCHECK is defined above for more detail. 
//// If the event is both null and invalid, then FMOD will simply log the error: "Event retrieval  
//// failed." In any case, whether the event actually plays or not, "hasStarted" is set to true, 
//// so that the playback status can be evaluated later if needed.
////----------------------------------------------------------------------------------------------*/

	public void StartEvent()
	{		
		if (evt == null || !evt.isValid())
		{
			CacheEventInstance();
		}

		if (evt != null && evt.isValid())
		{
			Update3DAttributes();
			ERRCHECK(evt.start());
		}

		else
		{
			FMOD.Studio.UnityUtil.LogError("Event retrieval failed: " + path);
		}
		
		hasStarted = true;
	}

////----------------------------------------------------------------------------------------------*/
//// void Play Function:
//// Play is a bit confusing in the context of this document. It does almost exactly the same 
//// thing as the StartEvent() function, but 1) has less safety checks, 2) does not set the 3D 
//// Attirbutes of the FMOD Event 3) does not set hasStarted to true, and 4) will not report an 
//// error if the event not null, but IS Invalid. When starting an event for the first time, always 
//// make sure to use StartEvent to ensure it actually gets retrieved and played properly. Then 
//// use Play if you have to play it again.
////----------------------------------------------------------------------------------------------*/
		
	public void Play()
	{
		if (evt != null)
		{
			ERRCHECK(evt.start());
		}
		else
		{
			FMOD.Studio.UnityUtil.Log("Tried to play event without a valid instance: " + path);
			return;			
		}
	}

//----------------------------------------------------------------------------------------------*/
// Functions for Stopping Audio Events and/or cleaning up resources:
// The following four functions (Stop, OnApplicationQuit, OnDestroy, and OnDisable) are meant for 
// the the purposes of stopping events and cleaning up resources when they are no longer needed.
//----------------------------------------------------------------------------------------------*/
////--------------------------------------------------------------------------------------------*/
//// void Stop Function:
//// I've modified this function slightly to provide for an overloaded method in addition to the 
//// way it's written in the original FMOD_StudioEventEmitter.cs document. Stop can optionally 
//// take a single bool argument; stop(true) will immediately stop the event with no DSP tail 
//// (such as reverb) and will immediately cut off any AHDSR modulation. Giving it the argument 
//// "false" will allow the fade out. No arguments being passed will simply set to an immediate 
//// stop mode.
////
//// Notice, that no error can ever be logged out, and this is because it is assumed that an 
//// event that can't be Stopped probably couldn't play to begin with.
////----------------------------------------------------------------------------------------------*/

	public void Stop()
	{
		if (evt != null)
		{
			ERRCHECK(evt.stop(STOP_MODE.IMMEDIATE));
		}		
	}
	
	public void Stop(bool immediateStop)
	{
		if (evt != null)
		{
			if (immediateStop)
			{
				ERRCHECK(evt.stop(STOP_MODE.IMMEDIATE));
			}
			if (!immediateStop)
			{
				ERRCHECK(evt.stop(STOP_MODE.ALLOWFADEOUT));
			}
		}		
	}
	
////----------------------------------------------------------------------------------------------*/
//// void OnApplicationQuit Function:
//// Simply set "isShuttingDown" to true when the application quits. This is used in OnDestroy.
////----------------------------------------------------------------------------------------------*/

	void OnApplicationQuit()
	{
		isShuttingDown = true;
	}

////----------------------------------------------------------------------------------------------*/
//// void OnDestroy Function:
//// OnDestroy is called whenever a MonoBehavior (such as this script) is destroyed. When 
//// destroyed, the script will determine the application is shutting down (OnApplicationQuit gets 
//// called before OnDestroy), and if it is, then just return - logging errors wont be necessary, 
//// neither will disposing of resources since Unity will automatically take care of it. If we're 
//// not shutting down, then we should log an FMOD Debug message (check out lines 49-54 of the 
//// FMOD_StudioSystem.cs for more info) of "Destroy Called". If the event is not null and is 
//// valid, then if the playback state is not stopped already, it will stop immediately. Whether 
//// it was stopped already or not, it will then release the event and dispose of its resources, 
//// then set "evt" to null in order for it to be re-used later if needed.
////----------------------------------------------------------------------------------------------*/

	void OnDestroy()
	{
		if (isShuttingDown)
			return;
		
		FMOD.Studio.UnityUtil.Log("Destroy called");

		if (evt != null && evt.isValid())
		{
			if (getPlaybackState () != FMOD.Studio.PLAYBACK_STATE.STOPPED)
			{
				FMOD.Studio.UnityUtil.Log("Release evt: " + path);
				ERRCHECK (evt.stop(FMOD.Studio.STOP_MODE.IMMEDIATE));
			}
			
			ERRCHECK(evt.release ());
			evt = null;
		}
	}

////----------------------------------------------------------------------------------------------*/
//// void Disable Function:
//// OnDisable is a function that I added and find relatively useful. It provides for the 
//// ability to treat enabling and disabling the MonoBehavior and treating that as akin to 
//// destroying the object. Of course, It's if the application is constantly enabling and 
//// disabling MonoBehaviors, you may not want this sort of behavior, so feel free to remove it. 
////----------------------------------------------------------------------------------------------*/

	void OnDisable()
	{
		if (evt != null && evt.isValid ())
		{
			if (getPlaybackState () != FMOD.Studio.PLAYBACK_STATE.STOPPED)
			{
				FMOD.Studio.UnityUtil.Log("Disabled evt: " + path);
				ERRCHECK(evt.stop(FMOD.Studio.STOP_MODE.IMMEDIATE));
			}
			
	         ERRCHECK(evt.release());
	         evt = null;
		}
	}
//----------------------------------------------------------------------------------------------*/
// Utility Function for Drawing Unity Gizmos:
// The following function, OnDrawGizmosSelected, will draw Gizmos into the Unity Editor windows. 
// Gizmos, in this case are those handy blue visual guides which show how far out in a spherical 
// radius a sound is emitted. To draw this, the script accesses the FMOD Asset "asset" and then
// determines if it is not null and is enabled. If it is, then it will create a temporary
// EventDescription called "desc" and sets it to null. It then uses the built-in C# script,
// FMODEditorExtension.cs to GetEventDescription according to the Asset ID tag. This goes into a 
// Dictionary and kind of gets a bit over my head but the important part is that the description 
// that is returned is accessed for its Maximum and Minimum Distances (that are set in the 3D 
// Panner area of the Event Macros area of the given events Deck, and then draws two wire 
// spheres: the size of the maximum and minimum distances in the 3D Panner. These cannot be 
// manipulated in Unity but can be updated by changing the minimum and maximum sizes of the the 
//event in code.
//----------------------------------------------------------------------------------------------*/
	#if (UNITY_EDITOR)
	void OnDrawGizmosSelected()
	{
		if (asset != null && enabled)
		{
			FMOD.Studio.EventDescription desc = null;
			desc = FMODEditorExtension.GetEventDescription(asset.id);

			if (desc != null)
			{
				float max, min;
				desc.getMaximumDistance(out max);
				desc.getMinimumDistance(out min);

				Gizmos.color = Color.blue;
				Gizmos.DrawWireSphere(transform.position, min);
				Gizmos.DrawWireSphere(transform.position, max);
			}
		}		
	}
	#endif
}

Hopefully this helps get folks on their way with FMOD programming, while I continue to refresh myself on it and come up with the core concepts I want to demonstrate. If you’re having trouble setting up FMOD in Unity, please feel free to check out my tutorial on Integration by clicking here, or if you want to see all of my writings and tutorials on FMOD, click here!