header image

FMOD Studio in Unity

The audio in Mini Metro is being designed by Module. He’s the genius behind Shatter and many other great-sounding titles, so we’re stoked to have him on board. His weapon of choice is FMOD Studio which has an official Unity plugin – handy!, but as a native plugin it requires Unity Pro, which we’re too tight to shell out for – stink!

Before I coughed up $4.5k for Pro licences for desktop, iOS, and Android, I had a bit of a poke around and, fairly easily, managed to get FMOD Studio working fine with non-Pro Unity.

There are two problems to tackle in getting a native library to work with Unity; binding the necessary C functions so they can be called from C#, and getting a standalone executable to locate the library at runtime. Binding is straightforward as C# has a brilliantly-simple way to do just that so I’ll cover that first.

C to C# function binding

C#’s DllImport API is fantastic compared to any other cross-language binding I’ve done in the past. It is located in System.Runtime.InteropServices.DllImport. All you need to do is prefix the C# prototype of the C function you want to call with a call to DllImport, and all the magic happens for you. So, for example, if I want to call the function to create an FMOD Studio system, which has the C prototype:

FMOD_RESULT F_API FMOD_Studio_System_Create(FMOD_STUDIO_SYSTEM **system, unsigned int headerVersion);

All I need to is declare the following prototype inside of a C# class:

private static extern FMOD_RESULT FMOD_Studio_System_Create(ref IntPtr system, uint headerVersion);

And we’re done! Well, almost. The FMOD_RESULT is obviously not a known type, so we need to take care of that. The C API defines FMOD_RESULT as an enum:

typedef enum {

We can easily emulate it in C# by declaring a similar enum:

public enum FMOD_RESULT : int {

Now we can pass FMOD_RESULT between C# and C and it will be translated correctly. So to call the function, all you do is:

IntPtr systemPtr = new IntPtr();
uint headerVersion = 0x00010206;
FMOD_RESULT result = FMOD_Studio_System_Create(ref systemPtr, headerVersion);
if (result != FMOD_RESULT.OK)
    // Panic!

Done! From here, abstracting the rest of the API is a trivial (if repetitive) task. I can upload my barebones FMOD Studio C# wrapper if required, just shoot me an email or tweet.

Locating the dynamic library

Unfortunately that’s not the hard part of integration; getting a standalone Unity build to locate the appropriate DLL is the tricky, platform-specific part.

At least it’s difficult on OS X. Good ol’ Windows doesn’t much care where it loads a DLL from, so just copy the required DLLs to the same directory as the executable, and you’re away laughing.

OS X is much more strict about which dynamic libraries it loads. This is to its credit, but as a developer it can be a pain! Here’s how I did it:

  1. When specifying the DLL name to DllImport, specify a path relative to @executable_path. In my case for FMOD Studio, I used @executable_path/../SharedSupport/libfmodstudio.dylib.
  2. After building the standalone OS X build, copy the required dylibs into the directory you specified in the previous step.
  3. Using install_name_tool, change the id of each of the dependent libraries to the same as the path you specified to DllImport. So for FMOD Studio, run install_name_tool -id @executable_path/../SharedSupport/libfmodstudio.dylib libfmodstudio.dylib.
  4. If those libraries themselves have any further dependencies (such as libfmodstudio.dylib depending on libfmod.dylib), copy them to the same directory. Using install_name_tool change their id, and the names they are referenced by in the dependent libraries, to something appropriate. So for FMOD, I changed both libfmod.dylib’s id and libfmodstudio.dylib’s reference to libfmod to @executable_path/../SharedSupport/libfmod.dylib.

Clear as mud!

So far I’ve managed to get FMOD Studio running on Windows and OS X. I don’t anticipate Linux to be too much trouble, assuming FMOD release binaries sometime soon! Android and iOS will be straightforward as Unity generates the projects, rather than building executables behind your back.