Using Fmod Doppler on Unity without a rigidbody

As I mentioned on this article, Fmod includes a built-in doppler feature but unfortunately it requires two things in order to work on Unity:

  • A rigidbody on the game object where the emitter is.

  • The game object needs to be moving via the physics engine, like for example using Rigidbody.AddForce or just using gravity.

This limitation has prevented me from really using the doppler effect since, on the games I have worked on, we usually move objects by code or using animations which means that Unity’s physics engine has no clue what the object’s velocity is, so in turn, Fmod doesn’t know either.

Additionally, we would also need to know the listener’s velocity, since in nature the doppler effect always takes into consideration the relative velocity between the listener (you and your ears driving in a car) and the emitter (another car’s horn).

So let’s jump into Unity and try to find a solution for this problem. I took most of the code from this thread on the Fmod forums.
Note: this work was done on the following versions so as time goes by, it could get more and more outdated:

  • Fmod Studio & Fmod Unity implementation plugin version: 2.02.04

  • Unity version: 2020.3.19f1

You can get this project on GitHub so you can copy the code more easily or play around with it. Here is the zipped project in case you prefer that.

Using Doppler with a rigidbody and the physics engine

Before we do anything else, let’s see how the system was intended to be used by default. As you can see in the video below, I have a very basic setup: I created a new, clean Unity project, integrated Fmod into it and created an Fmod project.

The Fmod project simply contains a 3D event with a looping testing sine wave tone. On the Unity project I just have a camera with the Fmod studio listener, a plane with no collisions and a cube. On the cube, I added a rigidbody and an Fmod emitter. As you can hear, at first we hear no doppler effect but activating the doppler feature on the event macro section and re-building banks makes the doppler effect work.

So now that we can see that Doppler is working when using gravity, let’s try switching the movement to just animation. As you can see, I disabled gravity, added an Animator to the cube and made an animation so it falls in a similar way as it does with the physics engine. You can clearly hear that there is no doppler in this case and we only get it back when we switch the gravity back on and disable the animator. So this is basically the problem we need to solve.

How Fmod receives velocity information

If we look at Fmod’s documentation, we can see that: “The Doppler property requires velocity in order to calculate how much Doppler adjustment needs to be applied. Velocity can be provided by the game engine or by calling EventInstance::set3DAttributes. In the case of the Unity and Unreal Engine integrations, this is usually set automatically to match the velocity of the associated Rigidbody or RigidBody.”

So this is how we are currently sending the velocity to Fmod, via the rigibody. But as the docs say, we can also provide this information manually which means we need to calculate the velocity ourselves but at least we don’t depend on the physics engine anymore. Let’s call this manually calculated velocity, “kinematic velocity”, since it doesn’t care about the forces applied, just the movement itself.

Let’s adapt the default Fmod emitter. I personally don’t use it since I have my own emitters but the default one is a good example to learn what’s going on.

Modifying StudioEventEmitter

First, let’s think about when we want to use kinematic velocity. In theory, everytime there is no rigidbody, we want to potentially use it but it would be a waste to do it if the event in Fmod is not even using doppler. On the other hand, sometimes we will have a rigidbody and we still want to use kinematic velocity since the object could be moved by just animation or code.

First, I thought about reading the event description to see if the event in question had doppler activated and turn on kinematic velocity based on that. The problem with this approach is that sometimes we may have doppler turned off, but we still want to send kinematic velocity information to Fmod to use it as a parameter for other things.

So my solution was to add a new option to StudioEventEmitter where you can set if you want to calculate kinematic velocity. This would be independent of having a rigidbody and also independent of the event having doppler activated. So firstly, let’s add a new bool variable to the public settings within StudioEventEmitter:

Since this class uses a custom made editor, we need to modify the editor class too:

As you can see, the class now has an additional option on the inspector.

We then declare a couple of variables on StudioEventEmmiter that we need to calculate the game object’s velocity:

As you can see, I’m using a VelocityVector3 class that is new to both Unity and Fmod. Let’s create this class on RuntimeUtils:

Next, let’s create a method to calculate the velocity on a given frame and update our velocity related variables:

On Update(), we want to call the previous method, but we only do it if we are using kinematic velocity for this particular event:

We are almost finished with StudioEventEmitter. The last thing we want to do is make sure that we attach the Fmod instance to the gameobject in a slightly different way when we want to use kinematic velocity. This is done within the PlayInstance() method.

So we modify the code to be:

As you can see, we are making sure we only attach the rigidbody if it exists AND we don’t want to use kinematic velocity. Otherwise, we use our new kinematic velocity. Here we are using a new overload on AttachInstanceToGameObject() that we don’t have yet. We will fix this in the next section.

RuntimeManager

This script takes care of updating things at runtime. We need to do a few modifications for our kinematic velocity to work. We first add two new variables to the AttachedInstance class. We do this so we can keep track of the velocities without a rigidbody.

Next, we create the overload we were missing before. As you can see, this attaches the Fmod instance to the game object and uses our kinematic velocity class instead of a rigidbody.

We now need to make sure we are updating the values on each frame. As mentioned earlier, this is accomplished by setting the 3D attribute by hand. So we look for the Update() method and we modify it like so:

Essentially, we are making sure we only update the attributes based on the rigidbody when it exists AND we are not using kinetic velocity. Otherwise, we update the values including kinetic velocity.

Notice that to be able to do this we need to overload the To3DAttributes() method on RuntimeUtils like so:

StudioListener

The last thing we need to do is also modify the Fmod listener. We need this since the Doppler effect is always based on the relative velocity of the emitter and listener. So we need to tweak StudioListener (or you could also create a child from it) to be able to measure its kinematic velocity.

We first add the same variables we added to StudioEventEmiiter. We also make sure we initialize the kinematic velocity variable if no rigidbody is found. You may need to modify this if your listener lives on a game object that has a rigidbody and you still want to use kinematic velocity.

We now copy the same method we used before for calculating speed in real time and we make sure we call it each frame if we are using kinematic speed.

The last thing we need to do here is to modify SetListenerLocation() so we can also use kinematic velocity as an option:

To make this work, we also need to overload SetListenerLocation() on the RuntimeManager:

And that’s all the code work finished!

Final Results

If everything went well, we should now be able to hear the doppler effect with no rigidbody and just moving the object via animations. As you can see, Fmod can now also get velocity values, which could also be useful to modify the audio based on them.

Success! These modifications could be done in many different ways depending on your needs so take my take as inspiration. If this doesn’t work for you or you have questions, feel free to contact me.

Remember that you can get this project on GitHub to play around with. Here is the zipped project in case you prefer that.

Fmod Built-in Parameters

Here is a quick view at the parameters that Fmod offers us out of the box. The great thing about them is that you don’t need to explicitly send the values to the event instance, the Fmod engine takes care of this for us.

So let’s have a look at how the Fmod docs describe them and then I will go into details to see how they can be useful and how i use them myself:

  • Distance built-in parameters track the distance between the listener and the event instance in game distance units.

  • Distance (Normalized) built-in parameters track the distance between the listener and the event instance as a fraction of the difference between the event's minimum and maximum distance. This allows anything automated or triggered by a distance (normalized) parameter to respond to an event's minimum and maximum distance properties changing as your game is played.

  • Direction built-in parameters track which way the listener is facing relative to the event instance.

  • Elevation built-in parameters track whether the event instance is above or below the listener.

  • Event cone angle built-in parameters track which way the event instance is facing relative to the listener.

  • Event orientation built-in parameters track which way the event instance is facing relative to the way the listener is facing.

  • Speed built-in parameters track how quickly the event instance is moving relative to the listener.

Distance parameters

Distance is an obvious and clear one but I have three thoughts or tips anyway. Firstly, this is measured in game units, so make sure you know how much a unit “feels” in your project. It could be metres, inches or light-years, it all depends on the scale of the game.

Secondly, you may think this parameter is kind of useless since the spatializer already attenuates audio with distance but sometimes you want to change the audio in different ways as the distance increases. For example, you could have a lowpass filter, a compressor or other effects that change your event with distance. You could also have aural LODs, that is, audio sources that play different loops depending on how close you are to the source. So imagine a machine: if you are very close, the audio is very detailed and intricate but as you move away from it, we only play simpler layers. Once you are far away, you can only hear the machine humming faintly.

Thirdly, sometimes you don’t want to attenuate audio using the spatializer. In this case, you can add a distance parameter and automate the master fader based on the distance. This allows for far more intricate curves and also has an additional advantage: if you do this on a gain plugin instead of on the fader, you can save everything as a preset to use on all similar events. Don’t forget to turn off the distance attenuation on the spatializer though, or you would be attenuating twice!

Distance (Normalized) is a cool one. This parameter always goes from 0 to 1, where 0 represents the event’s min distance, while 1 is the max distance. So you can automate your event using distance like I explained above but with the additional advantage that if you need to change the min or max distance after the fact, you don’t need to tweak all your curves since they are already relative to those values.

Elevation

This one is useful if you want to change your event’s audio based on the vertical distance between source and listener. Values go from -90 to 90. Negative values mean the sound is below you, while positive ones indicate the sound is above you. Not sure why the values go from -90 to 90, since there are no angles involved, as far as I know.

This parameter can be useful to achieve some poor man’s vertical occlusion. If you have some sounds coming from a different floor in a building, you can use this to approximate some occlusion although it has obvious limitations.

Speed

This one, despite what the docs say, is now divided in two versions: relative and absolute. The first one is the same one the docs mention, the relative speed between the listener and source. As you may imagine, the absolute version ignores the listener and just measures the absolute source speed.

Is important to note that this would only work if the audio event is attached to a GameObject and moved using the physics engine. That is, it needs to have a rigid body in the case of Unity. If you move an object by code using the transform position or you move it with an animation, that would not work and the speed will always be 0. Remember that the same also applies for the built in doppler effect Fmod has.

Orientation parameters

These can be tricky to wrap your head around. They basically define the relative facings of the listener and audio source. We have three different parameters: Direction, Event Cone Angle and Event Orientation. Let’s try to understand the difference between them.

Direction takes into account where the listener faces, regardless of the orientation of the audio source . To see what direction really measures, let’s draw a line from our audio source to the listener (shown in green). Direction is the angle between this line and where the listener is facing (red arrow).

Is important to note that direction doesn't care about where the audio source is facing. It is measured in angles, so as you can see, ‘0’ means that the listener is looking directly at the source. Negative values indicate that the source is to the left of the listener, 90 being directly to the left. -90 is just the opposite, directly to the right. To represent the listener looking in the opposite direction we use 180 or -180, both mean the same thing.

Direction is useful to make things louder when the listener is facing them, particularly when an audio source emits audio in all directions. So, for example, a firecamp would not be louder from any particular direction, the only thing that would make it louder for a listener, apart from distance of course, is the way the listener is facing. From the same position, if you are looking directly at the fire, the sound would be louder than with your back to it.

Event Cone Angle is almost the reverse of Direction. We draw a line between audio source and listener (again in green). The event cone angle is the angle between this green line and where the audio source is facing.

Again, take into consideration that we don’t care about the angle the listener is facing here. Something important to keep in mind is that the event cone angle doesn’t differentiate between listeners being to the left or right of the source which is why the values go from 0 (listener is facing the source) to 180 (listener is looking in the opposite direction the source is). Thus, 90 would represent the source being to the side of the listener, no matter which side.

Event cone angle is usually used to make events louder when the source is facing them, while making them quieter or more muffled when the source is not in front of them but to the side or behind. This could initially sound similar, maybe too similar to how we use direction. It was to me. The key to see the difference is that here we don’t care about the listener orientation, only where the audio source is facing. So let’s say our audio source is a person talking. Definitely the sound changes as you move around the person since the human voice is somewhat directional, especially the higher frequencies. Being in front of the person would be the loudest, with an angle of 0, while being behind them would be the quietest, with an angle of 180.

Event Orientation is finally the last parameter we can use. This one takes into account the angle between where both the source and listener are facing. This may sound like a useful thing but it really isn’t if you think about it. Let’s have a look:

As you can see, here there is no green line. That means that we don’t care about the relative positions of the source and listener, we just care about where they are facing, relative to each other. If you look at the two 180 examples, you will see that two very different situations (opposites, actually) share the same value which may feel odd.

This is why this parameter is not not used very much. I have never found a situation to use it myself.

Orientation TLDR: Use direction to describe how the listener orientation angle to the source changes the audio. Makes sense with non-directional audio sources (like a fire camp) and directional listeners (like human ears). On the other hand, use event cone angle to describe how the audio source orientation angle to the listener affects the audio. Useful for directional audio sources, like the human voice. Of course, if you have a directional audio source AND a directional listener, you should use a combination of both parameters. On the other hand, event orientation can be ignored for most situations.

Figuring out: Ambisonics

Here are my notes about the world of Ambisonics. This is a new area to me so, following this blog’s phylosophy, I will try to learn by explaining. Take this as an introduction to the subject.

The basic idea

We usually think about audio formats in terms of channels. Mono and stero being the most basic and used ones. If we open up the 2D space even more, we get surround audio like 5.1 and 7.1. Finally, the last step is to use the full 3D space and that’s where Ambisonics comes in.

The more complexity and channels we have, the harder is to make systems compatible between each other. In order to solve this, Ambisonics trascends the idea of channels and uses the concept of sound fields which represent planes of audio in 3D space.

This then allows to keep the aural information in a “speaker arrangement agnostic format” that can be decoded into any amount of speakers at the time of reproduction.

M/S Format

These planes of audio are represented in a special format called B-Format. You can think of this format as a natural extension of the M/S format so let’s start with that.

To get a M/S recording, we first use a figure of eight microphone facing sideways to the source (this is the “side”). This microphone will pickup the stereo information. At the same time, we use a cardioid microphone facing the source (this is the “mid”).

Once we want to decode these signals into stereo, we just need to sum mid and side to obtain the stereo left and then sum mid and the side with polarity reversed to obtain the right side. If you think about this, you realize that the “side” signal is basically a representation of the difference between left and right.

But why would we want to record things this way? Why not just record in stereo with a X/Y technique or similar? Recording using M/S has a few advantages. Firstly, we get automatic mono compatibility since we have the mid signal which we can use without fear of the phase cancellations that would happen if we sum the channles from an X/Y format. Additionally, since we can decode the M/S recording into stereo after the fact, we can control how wide we want the resulting stereo signal to be by just adjusting the balance between mid and side during decoding.

B-Format

Amisonics takes this concept and pushes to the next dimension, making it 3D by using additional channels to represent height and depth. B-format is then built with the following channels:

  • W: Contains the sound pressure information, similar to the mid signal in M/S. This is recorded with a omnidirectinal microphone.

  • X: Contains the front minus back pressure gradient. Recorded by a figure of eight microphone.

  • Y: Contains the left minus right pressure gradient, similr to the side signal in M/S. Recorded by a figure of eight microphone.

  • Z: Contains the top minus bottom pressure gradient. Recorded by a figure of eight microphone.

Note: A-Format is how we would call the raw audio from an ambisonic recording, that is, the individual signals from each microphone, while B-Format is used when we have already combined all these signals into a unique set.

Ambisonic Orders

The top row shows the W component, while the second one shows X, Y and Z. Additional rows show higher ambisonic orders for higher resolutions.

Using the B-format described above works but comes with some drawbacks. The optimal listener position would be quite small and results won’t be very natural outside it. Also, diagonal information is not very accurate, since it has to be inferred from the boundary between planes.

A solution to these issues is to increase resolution by adding more selective directonal components which instead of using traditional polar patterns would use other specific ones resulting in a signal set that contains denser aural information.

There is really no theoretical limit in how many additional microphones we can add to improve the resolution but of course there are clear prctical limits. For example, a third order ambisonics set, would use 16 tracks so is easy to see how hard drive space and microphone placement can quickly become a problem.

Decoding B-Format

Regardless of the number of ambisonics orders we use, the important thing to keep in mind is that the resulting recording will be not channel dependent. We can build the sonic information of any point in a 3D sphere by just knowing the angles to this point.

This allows us to create virtual microphones in this 3D space with which we can then match with any number of speakers. This is very powerful because once we have an ambisonics recording we can then play it on any speaker configuration preserving as much as the spatial information as the reproduction system allows.

If the final user is using headphones, a binaural signal would the result of the decoding while the same source files can be used to decode a 3D Dolby Atmos mix in cinemas.

Nowadays, you can find a big selection of ambisonic plugins for your DAW so you can play around with B-Format files including coding and decoding them in any other mutlichannel format you can imagine.

Use in media

Ambisonics was created in the 70s but has never been used much in mainstream media. This is now changing with the advent of VR experiences where 3D audio makes a lot of sense since the user can move around the scene focusing on different areas of the soundscape.

In the area of cinematic experiences ambisonics achieves a similar result as Dolby atmos or Auro-3D but using different methods. See my article about atmos to know more about this.

Google’s Resonane Audio allows you ti use ambisonics in Fmod

Regarding videogames or just generally interactive audio, ambisonics is a great fit. You can implement B-Format files in middleware like Fmod or Wwise and also game engines like Unity. This gives you the most flexibility since the ambisonics format will be decoded in real time into whatever the user is using to reproduce audio and this decoding will react in real time to their position and direction which is particularly awesome for VR.

In closing

There is much more to learn about this so I hope i get the chance to work with Ambisonics soon. I’m sure there are many details to keep in mind once you are hands on working with this formats and will try to document what I learn on this blog as I go.

Introduction to the FMOD API

These are some notes and pointers about using the FMOD API, particularly if you (like me) don’t have a lot of previous programming experence. I won’t tell you anything that is not already in the official documentation but take this a condensed reference to use when coding or just to get familiar with the API.

Getting started

You can find the official documentation on the FMOD website. In case you don’t know, API stands for Application Programming Interface and is pretty much a bridge between two different computer environments like two different pieces of software. In this case, the FMOD API serves as the bridge between a game engine like Unity which scripts are written in C# and the FMOD engine itself.

The FMOD API can speak C, C#, C++ & JavaScript although not all functions are available in all languages. I will give C# and Unity examples here but these can be easily translated into other languages and engines.

Do you even need to use the API?

It depends. Out of the box, FMOD can be used on Unity or Unreal without much code work. You will be able to load banks and play audio events with just the components that come with the plugin but as soon as you need to do more complex things, you will probably need to use the API, at least in a few areas.

emitter.png

Studio vs Core API

Before diving in, you will see that FMOD differentiates between Studio & Core API. Most likely, you will just use the Studio API since it talks to Fmod Studio making things much easier for us sound designers.

The Core API, on the other hand, is completely standalone, allowing to integrate the fmod engine in a game without even using Fmod Studio. This is aimed for more bespoke integrations and deeper functionality that in most cases is not really needed but it is good to know it is there as an option.

So for now on, I will focus on the Studio API since is the one I’ve used myself.

Studio API Structure

On a high level, the studio API uses a series of concepts and classes that we should famliarize ourselves with. The main “brain” is the class RuntimeManager which will create and initialize an Studio::System instance which, in turn, will be the class we will use to call engine wide functions like setting global parameters.

By default, you don’t need to do anything for this to work as the fmod implementation in Unity or Unreal will get his ready for you. Studio::Bank can be then used to operate on the different banks themselves while Studio::Bus and Studio::VCA will allow you to get and set data on the mixer elements.

The concept of events in FMOD Studio is mapped to an Studio::EventDescription so if you want to do anything related to individual events that is what you will use. But keep in mind that you can have many instances of an event, so the API allow us to affect each of these through Studio::EventInstance. To be able to do this, you will need to cache a reference to each instance you play if you want to have full control of them. Usually, we don’t need to cache one-shots instances since we don’t need to affect them once played.

Also, remember that snapshots, when played, are also considered event instances so you can have all the same functionality for them and I would personally recommend creating a dedicated snapshot manager to keep track of them.

With that basic structure out of the way, let’s see what the API offers.

Studio API: Types

FMOD defines a bunch of types which will be very useful to get information about the audio engine. This are just a few commonly used types but there are many more.

Function Example Description Parameter Description FMOD.Studio.PARAMETER_DESCRIPTION Contains all the information in a parameter, including name min and max values, etc.. Loading State FMOD.Studio.LOADING_STATE Useful to know the loading state of different things, like banks and make sure we only use them when thay are loaded. Playback State FMOD.Studio.PLAYBACK_STATE; This one is SUPER useful so you can check if an event is playing, stopped, sustaining, etc... 3D Atrributes FMOD.ATTRIBUTES_3D The 3D position of an event or listener. This is a struct of simple vectors so you may need to convert it to something more usable like a Vector3.


Studio API: System

These calls allow you to talk to fmod in a general way, that would affect the whole fmod audio engine. Here are some of the most useful functions with some examples:

Function Example Description
Load Bank FMODUnity.RuntimeManager.LoadBank(bank, bool); Where "bank" is simply a string with the bank name. Make the bool true if you want to pre-load the sample data. I'm not sure why this can be done with just the RuntimeManager without using StudioSystem.
Unload Bank FMODUnity.RuntimeManager.UnloadBank(bank);
Get Bank FMODUnity.RuntimeManager.StudioSystem.getBank(path, out bank); Use this to grab a reference to a specific bank. "path" is the bank name (a string) and you will get a "Bank" struct type.
Get Bank Count FMODUnity.RuntimeManager.StudioSystem.getBankCount(out int count); Get the number of banks already loaded.
Get Bank List FMODUnity.RuntimeManager.StudioSystem.getBankList(out Bank[] array); Get an array of the type "Bank". Use with the one above if you need to get a reference to all loaded banks.
Get Listener Attributes FMODUnity.RuntimeManager.StudioSystem.getListenerAttributes(int listener, out FMOD.ATTRIBUTES_3D attributes, out FMOD.VECTOR attenuationposition); Use this to get a reference to the listener position which can be useful to calculate distances to emitters. The default listener is in index 0. There is also a set version of this function..
Get a Bus Reference FMODUnity.RuntimeManager.StudioSystem.getBus(path, out Bus bus); Get a bus reference so you can then use it. "path" is the bus string name. "bus:/" will always be the name of the master bus. You can also find a similar function for VCAs.
Get an event description FMODUnity.RuntimeManager.StudioSystem.getEvent(path, out EventDescription _event); This is very handy if you want to do something with an specific event. As usual, "path" is the string name.
Set a global parameter FMODUnity.RuntimeManager.StudioSystem.setParameterByName(name, value, ignoreseekspeed); Sets a value to a global parameter where "name" is a tring with the name of the parameter, "value" is a float and "ignoreseekspeed" is a bool.

Studio API: Bank

We won’t be able to do much until we have our banks loaded. Keep in mind that the master bank always needs to be loaded as this contains mixer information like buses, vcas or snapshots. Other than that, all other banks can be loaded one by one as needed. I talked about how to get a reference to a particular bank above, let’s now see what you can do with that reference:

Function Example Description
Get loading state Studio.Bank.getLoadingState(out LOADING_STATE state); We can check if a bank has finished loading before doing anything with it.
Load Sample data Studio.Bank.loadSampleData(); This will load all the audio files themselves into memory before any audio is played. Use this if the events are very time sensitive or you want to trade CPU for memory. There is also an unload method.
Unload bank Studio.Bank.unload(); Use this if you are sure you don't need the events contained on this bank anymore so we can save some memory.
Get the event count Studio.Bank.getEventCount(out int count); Check how many events the bank has.
Get event list Studio.Bank.getEventList(out EventDescription[] array); Use with the above method to get an arrray of event descriptions in case you then want to do something with them.

Studio API: EventDescription

You can find here all functions related to each specific event set in FMOD Studio. Before you use this, remember that you need to grab a reference to the event description using the method described in the section above so you can only do this once the bank containing the event is fully loaded. Once you have it, you can get some very usefu information from it. Here are some examples:

Function Example Description
Create Instance FMOD.Studio.EventDescription.createInstance(out EventInstance instance); Use this to create an instance which you must do before you play the event. It can be a good idea to cache the instance for use later.
Load Sample Data FMOD.Studio.EventDescription.loadSampleData(); This is very handy if you want to load the sample data for an event before you play it. Particularly useful for time sensitive events.
Is 3D FMOD.Studio.EventDescription.is3D(out bool is3D); Check if the event is 3D whcih would be determined by the event having an spatializer. Nice one if you create your own emitter or player.
Is Valid FMOD.Studio.EventDescription.isValid(); Use this one to make sure the event is not null and can be used.
Is Snapshot FMOD.Studio.EventDescription.isSnapshot(out bool snaphot); Check if a particular description is a snaphot.

Studio API: EventInstance

As mentioned before, we need to create an event instance in order to be able to play it or just do anything with them. Once this is done, we can have full control of the instance. Here are some useful methods:

Function Example Description
Play Instance FMOD.Studio.EventInstance.start(); Just play the instance. Basically, this will start the timeline, as set in fmod studio.
Stop Instance FMOD.Studio.EventInstance.stop(FMOD.Studio.STOP_MODE); Stops an instance. As you can see, we need to pass a stop mode which will determine if we respect any fades set in studio.
Release Instance FMOD.Studio.EventInstance.release(); This will tell the fmod engine that this instance can be deleted from memory. Use this after stopping an instance if you don't need it anymore. Important to prevent memory leaks.
Get playback state FMOD.Studio.EventInstance.getPlaybackState(out FMOD.Studio.PLAYBACK_STATE state) Use this to know the current state of the instance. Very useful if we want to check if the instance is playing before trying to stop it.
Set 3D Attributes FMOD.Studio.EventInstance.set3DAttributes(RuntimeUtils.To3DAttributes(position)); Sets the instance to a particular 3D position. Used to play audio in some specific place.
Attach to game object RuntimeManager.AttachInstanceToGameObject(instance, gameObject.transform, rigidBody); Use this for 3D events if, instead of playing in an specific place, you want to attach the sound to a game object.

Studio API: Bus & VCA

We can interact with the game’s mix by getting and setting values on buses and vcas. Check the system section to see how we can get a reference of a particular bus or vca but basically, we would need to know its name (as it was names in FMOD studio) as a string value so it could be a good idea to keep a reference of these somewhere in your code.

Let’s see a few examples of things we can do with buses and vcas:

Function Example Description
Set Bus volume Studio.Bus.setVolume(float volume); Sets a particular volume for this bus. There is also a get method.
Stop all events Studio.Bus.stopAllEvents(STOP_MODE mode); Stops all the events within a bus. Useful if you want to make sure all audio within a particular bus stops when unloading a level, for example. Notice that we need to pass the stop mode we wish to use.
Pause all events Studio.Bus.setPaused(bool pause); This will pause or unpause all the events contained in a bus. Use this for pause menus, for example.
Set VCA volume Studio.VCA.setVolume(float volume); Set volume on a VCA. Nice for the audio level settings.

In conclusion

I think if you understand how all the above work, that would be a very good start. Check out the FMOD reference docs for many useful examples and dont’ hesitate to drop me a line if you have any questions, I know some of this can be obscure until you use it for a while.

Brief Source Control guide for Audio Peeps

Here is a summary of ideas and concepts about Source Control or Version Control that I wished I knew when I starting out in game audio since they can get a bif confusing.

What is Source Control?

Basically, It is a system used within software development to share and keep track of code and other assets. Since many people will have access to the same files, you can see how this can create conflicts if two people try to change the same thing at the same time. Source Control has features and workflows to allow people to safely work without conflicting with each other.

It also allows you to work with different versions of the same application which share some parts of the code. At the same time, in functions as an advanced backup because all the work is both locally on every developer computer and on a server somewhere. Because of this, is easy to go back to a previous version if needed, like if something breaks, for example.

So imagine something like Google Drive or Dropbox but far more advanced so multiple users can seamlessly work at the same time and with different partial collections of files from a common pool in the cloud.

In summary, Source Control is a fancy way of sharing files between people in a way that allows for multiple versions and conflicts can be minimized and/or resolved when they arrive.

Git

This is one of the most popular pieces of software to do Source Control and you will probably see it all around. Since it was developed by Linus Torvalds (yeah the guy who is the reason Linux is called that way), it is a free and open source so anyone can use it.

As far as I can see, Git was 87.2% of Source Control software in 2018 so it is clearly the King.

What can be confusing at first, is that Git, more than an application itself, is a system or protocol. Git can be used on the terminal but more usually people use a Client, which is a dedicated App with a GUI (Graphical User Interface) so it is easier to use.

Fundamentally, you can use Git purely locally but usually it is hosted online so many people can access the same repository. So you need some server somewhere to do this.

In conclusion, Git seems to be the most used Source Control software and it is really a protocol that can be used by different clients and using different online services for hosting. Let’s see these:

Git Hosting & Clients

GitHub: This is a company owned by Microsoft which offers free online hosting for Git repositories. Many important open soruce projects are on Github, like Bitcoin. For commercial and more complex projects, Github also have paid options.

GitHub Desktop: This is a client, a software with a GUI that uses the Git protocol and can be used with Github hosting or really any other Git hosting (Even local).

GitLab: This is another popular online hosting service for Git projects.

Bitbucket: This is yet another online hosting service for Git owned by Atlassian, who also owns Jira (a task management) and Confluence (wikis) which are also popular in the game development industry.

Sourcetree: This is a popular client with a GUI to work with Git repositories and it is also owned by Atlassian.

Fork: Yet another client to use Git with.

TortoiseGit: One more Git client which integrates with the Windows File Explorer.

There are many, many more hosting services and clients but those above are some of the most popular.

Apache Subversion (SVN)

This is a completely different ecosystem and procol for source control. It uses similar concepts as Git but on other aspects is quite different. I’ve seen SVN used for larger files like art and audio within game development.

TortoiseSVN is a popular SVN client which integrates with the Windows File Explorer.

Perforce or P4

This another Source Control protocol software, quite popular in software and game development.

Terminology & Workflows

Now that we know a bit about the ecosystem, let’s see some of the concepts that Source Conctrol uses. This can be applied to any system but is mostly oriented towards Git.

repository is like a project that you create withing the Git system. So an app or a game will generally be one repository that is then shared by all the users.

When you want to get the repository in your local computer this is called Clonning. When doing this, you choose a folder in your drive and the Git client will download all the content. Once you do this, you have access to all the files, or to be precise to a version of all these files from the moment you did the clonning.

Although you are at first clonning the whole repository, while you work you are always dealing with branches. These are different versions of the software or game that are used by the team. Usually, there is a stable main branch called master or develop which functions as the principal branch where all other branches usually eventually point to.

After you clone the repo, if someone else updated it later, you will get “behind”. Source Control doesn’t usually work like Drive or Dropbox, where things are automatically updated. In contrast, you have update it manually. This may seem like a weakness but it is actually a strength. You need to have full control of when you are behind, ahead or just in sync with the server’s repository, depending on the situation.

Generally, if you are not working on anything at the moment, you want to be up to date with the master branch. For this, you switch to that branch (you check out the branch) and use the Pull action, which just pretty much checks if there is new stuff on the server and downloads it. In the case of SVN, this is action is called Update, which makes sense.

Keep in mind that when getting the latest version, you don’t get the whole thing again, just whatever is new or was changed, which makes things much faster. But what happens of you are the one who makes the changes? In this case, the simplest thing to do is to just send your changes to the server. In order to do this, git has two different steps which can be confusing. Before we go into those, we need to find whatever files we have changed and select them to make sure those are the changes that we want to upload. This part is important because usually there will be other local changes that we don’t really want to get into the server.

So after making sure that we have chosen the correct changes, we need to do these two step which are usually done at once but is important that you know the difference between them. The first is a Commit. This is just saying to the repo, hey I want to change these files and here is a brief description of what this change does. Once you do a commit, you have updated your local version of the branch where you are working on, but the server (or your co-workers) still have no idea that this change has occurred. The next step is a Push and that’s just the action of uploading your changes to the server for everyone to see.

As you can see, in the heart of this Commit and Push workflow is the isea of a Change, that is, the files that you have modified in respect to the server version that sits in the repo. Sometimes, you will make unintended changes which will break things. Since you are working with Source Control, you can always revert these changes, which will change the file back to its original state, which should be the same that exists in the server so no harm done.

Something else that it is important to know: You don’t usually work directly on the master or develop branch. This branch is usually reserved for an estable version of the game or software so people shouldn’t directly push things to it. That’s why we use branches. You would usually do your work in a branch you create, check that all works as intended and then push your changes to a server version of your own branch. Once that is done, you will create a Pull Request, from your branch to the estable one and usually someone with more seniority will check that all looks good and then accept the pull request. Once done, the result is sometimes called a Merge, since your branch is merging into the master branch.

And that’s all I have, it was quite a lot at once if you are new to this but I hope this works as an introduction from which you can expand as you get more and more into game development.