FMOD in C#… it’s a pain to set up. here’s how I did it:

I’ve been having a lot of trouble playing music in Mysterious Space, due to bugs with MonoGame. these bugs range from failing to properly loop songs to crashing the game! music-related bugs have plagued MonoGame for a while (like, years), so waiting for them to get fixed… yeah… dunno about that.

in light of these issues, DDRKirby(ISQ) recommended FMOD to me. it’s a music-playing library that’s been around for a long time. it’s a mature, stable library.

that being said, getting it working in C# has always been something of a problem. there are a lot of wrappers that you can find online, but most have not been updated in a couple years; FMOD comes with a wrapper of its own, but documentation is poor.

I was finally about to get it working in Mysterious Space, though, so for anyone out there googling “FMOD C# example”, as I was trying to do, this is for you.

a few things to mention about my setup:

  • I will be using FMOD’s built-in wrapper
  • I’m using Visual Studio 2012
  • I’m compiling for mixed platforms (32 AND 64-bit; this makes using FMOD a little more “exciting”)

Adding the .cs Files

you can’t add FMOD as a “Reference” for your project. instead, you need to add the DLL files to your project’s root, and include a few .cs files that come with FMOD.

let’s start with the .cs files. they can be found in FMOD’s install directory, under api\lowlevel\inc, for example, C:\Program Files (x86)\FMOD SoundSystem\FMOD Studio API Windows\api\lowlevel\inc. add all the .cs files there to your project.

open up fmod.cs and check out that #if WIN64 block. this is telling the fmod wrapper which dll to include. if you’re happy compiling strictly for 32-bit, or strictly for 64-bit, leave this code alone, and #define WIN64 if needed (you can do this either right in the file, or through the project’s settings, or edit out the #if block, or whatever).

if you want your program to run on both 32 AND 64-bit platforms, you’re in for a touch of extra coding which we’ll get to in a moment. for now, though, remove the #if block entirely, leaving only the line public const string dll = "fmod"; (define-related code is determined at compile-time, but we need to include the right DLL during run-time!) technically you don’t have to do this (assuming you DON’T #define WIN64), but I didn’t want to leave in code that suggested something platform-specific was going on. it’s up to you.

Adding the .dll File(s)

if you’re compiling only for 32-bit, or only for 64-bit, find the appropriate dll and add it to the root directory of your project. for 32-bit, the file is fmod.dll; for 64-bit, it’s fmod64.dll. both files are in FMOD’s install directory, under api\lowlevel\lib.

fmod-dllsif you’re compiling for BOTH, you’ll need to do things a little differently: you’ll need to add both files, but they both need to be called fmod.dll (fmod64.dll will be renamed; remember how we modified fmod.cs so that it always looks for “fmod.dll”?). this means putting them in different directories. for myself, I created an “FMOD” directory, and then created directories named “32” and “64” inside that, placing fmod.dll into “32”, and fmod64.dll into “64” before renaming it to fmod.dll.

whichever files you included, make sure that their “Copy to Output Directory” property is set to “Copy if newer” (or “Copy always”). if you leave this property to “Do not copy”, the file(s) will not be copied over to the directory where your program’s executable is placed when compiling.

Actually Using FMOD

I wrote a class responsible for handling music, from loading it, to playing it. I feel like examples are more helpful than long explanations, so here’s the class I wrote. You’ll need to modify it; most of those modifications should be obvious. Further explanation, and example usage, are below the code.

using System;
using System.Runtime.InteropServices;

namespace MysteriousSpace
{
    public class MusicPlayer
    {
        public const int NUM_SONGS = 14;

        public const int SONG_TITLE_MENU = 0;

        public const int SONG_SECTOR_MAP = 1;

        public const int SONG_DESERT = 2;
        public const int SONG_FOREST = 3;
        public const int SONG_OCEAN = 4;
        public const int SONG_BARREN = 5;
        public const int SONG_ICE = 6;
        public const int SONG_LAVA = 7;
        public const int SONG_MINING = 8;
        public const int SONG_ASTEROID_BELT = 9;
        public const int SONG_ENCOUNTER_HUMAN = 10;
        public const int SONG_ENCOUNTER_CYBORG = 11;
        public const int SONG_ENCOUNTER_AI = 12;
        public const int SONG_ENCOUNTER_ALIEN = 13;

        private FMOD.System FMODSystem;
        private FMOD.Channel Channel;
        private FMOD.Sound[] Songs;

        private static MusicPlayer _instance;

        public static MusicPlayer Instance { get { return _instance; } }

        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        public static void Init()
        {
            if (Environment.Is64BitProcess)
                LoadLibrary(System.IO.Path.GetFullPath("FMOD\\64\\fmod.dll"));
            else
                LoadLibrary(System.IO.Path.GetFullPath("FMOD\\32\\fmod.dll"));

            _instance = new MusicPlayer();
        }

        public void Unload()
        {
            FMODSystem.release();
        }

        private MusicPlayer()
        {
            FMOD.Factory.System_Create(out FMODSystem);

            FMODSystem.setDSPBufferSize(1024, 10);
            FMODSystem.init(32, FMOD.INITFLAGS.NORMAL, (IntPtr)0);

            Songs = new FMOD.Sound[NUM_SONGS];

            //LoadSong(SONG_TITLE_MENU, "");
            LoadSong(SONG_SECTOR_MAP, "Set a Course [Seamless]");
            LoadSong(SONG_DESERT, "Uncharted Expanse [Seamless]");
            LoadSong(SONG_FOREST, "Mystic Depths [Seamless]");
            LoadSong(SONG_OCEAN, "Dreamscape (Mysterious Space Edit)");
            //LoadSong(SONG_BARREN, "");
            LoadSong(SONG_ICE, "Always (Mysterious Space Edit)");
            LoadSong(SONG_LAVA, "Afterglow [Seamless]");
            LoadSong(SONG_MINING, "Eternal Excavation");
            LoadSong(SONG_ASTEROID_BELT, "Not the Same Level (Mysterious Space Edit)");
            //LoadSong(SONG_ENCOUNTER_HUMAN, "");
            //LoadSong(SONG_ENCOUNTER_CYBORG, "");
            //LoadSong(SONG_ENCOUNTER_AI, "");
            //LoadSong(SONG_ENCOUNTER_ALIEN, "");
        }

        private void LoadSong(int songId, string name)
        {
            FMOD.RESULT r = FMODSystem.createStream("Content/Music/" + name + ".flac", FMOD.MODE.DEFAULT, out Songs[songId]);
            //Console.WriteLine("loading " + songId + ", got result " + r);
        }

        private int _current_song_id;

        public bool IsPlaying()
        {
            bool isPlaying = false;
            
            if (Channel != null)
                Channel.isPlaying(out isPlaying);

            return isPlaying;
        }

        public void Play(int songId)
        {
            Console.WriteLine("Play(" + songId + ")");

            if (_current_song_id != songId)
            {
                Stop();

                if (songId >= 0 && songId < NUM_SONGS && Songs[songId] != null)
                {
                    FMODSystem.playSound(Songs[songId], null, false, out Channel);
                    UpdateVolume();
                    Channel.setMode(FMOD.MODE.LOOP_NORMAL);
                    Channel.setLoopCount(-1);

                    _current_song_id = songId;
                }
            }
        }

        public void UpdateVolume()
        {
            if(Channel != null)
                Channel.setVolume(Settings.GetInstance().MusicVolume / 100f);
        }

        public void Stop()
        {
            if (IsPlaying())
                Channel.stop();

            _current_song_id = -1;
        }
    }
}

let’s talk about CREATING the instance first.

I implemented MusicPlayer as a “singleton”: it’s constructor is private, but an instance is available via MusicPlayer.Instance.

I’ve got some FUNNY BUSINESS surrounding the instantiation of MusicPlayer.Instance, however:

the following code is required when compiling for both 32 and 64-bit systems. before using MusicPlayer.Instance, you will need to call MusicPlayer.Init().

        private static MusicPlayer _instance;

        public static MusicPlayer Instance { get { return _instance; } }

        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        public static void Init()
        {
            if (Environment.Is64BitProcess)
                LoadLibrary(System.IO.Path.GetFullPath("FMOD\\64\\fmod.dll"));
            else
                LoadLibrary(System.IO.Path.GetFullPath("FMOD\\32\\fmod.dll"));

            _instance = new MusicPlayer();
        }

the conditional LoadLibrary calls are the magic that make running on 32 and 64-bit platforms possible. you may notice the [DllImport("kernel32.dll")] call, and wonder why that’s always 32-bit… it isn’t! confusingly, even in 64-bit Windows, this file is still called “kernel32.dll”!

if you are ONLY targeting 32 OR 64-bit, however, you can replace the above mess with this:

        private static MusicPlayer _instance = new MusicPlayer();

        public static MusicPlayer Instance { get { return _instance; } }

all that funny business surrounding LoadLibrary is gone, and you no longer need to call MusicPlayer.Init() (giving us a much cleaner singleton!)

with that stuff out of the way, what other modifications will you need to make before using MusicPlayer in your code?

the most obvious changes would be to the CONSTs defined at the top. so obvious, I won’t even explain 😛

the MusicPlayer() constructor needs similarly-obvious modifications to load up the proper songs. also, make sure to update the LoadSong(...) method to point to whatever directory your music files are stored in.

finally, check out the UpdateVolume() method. for my game, I keep all the game’s settings in a singleton, which UpdateVolume() is looking at to determine how to set the music’s volume. you’ll need to change this method to grab the volume from wherever you keep it. (if you don’t have such a setting yet, just do Channel.setVolume(1f); note that the volume goes from 0.0f to 1.0f!)

oh, and you don’t HAVE to, but you’ll probably want to change the namespace from “MysteriousSpace” to whatever you’re using for your application.

alright: so how do we actually play music in your application?

  • first, if you’re doing the whole 32+64-bit messiness, call MusicPlayer.Init() before calling any other MusicPlayer method!
  • call PlaySong(...) to play a song, passing in the song id you want to play, for example: MusicPlayer.Instance.PlaySong(MusicPlayer.SONG_TITLE_MENU); the music will loop automatically (check out the PlaySong method to see how this is accomplished; hint: it’s accomplished easily!)
  • if you call PlaySong(...) while another song is playing, the previous song will be stopped, and the new one started! handy!
  • you can call MusicPlayer.Instance.Stop() to stop the music.
  • you can call MusicPlayer.Instance.IsPlaying() to see if a song is currently playing; you may not ever need to use this method, but it’s used internally by other MusicPlayer methods.
  • remember to call MusicPlayer.Instance.UpdateVolume() whenever you need to update the music volume. (for example, in my game, the player can access volume settings during gameplay; when the player changes the volume here, MusicPlayer.Instance.UpdateVolume() is called so that the volume change is applied immediately.)
  • call MusicPlayer.Instance.Unload() to unload FMOD as part of your application’s shutdown/exit code.

All Done!

I had a lot of trouble getting FMOD to work in my project; hopefully this article will save some other people the same trouble I had!

3 thoughts on “FMOD in C#… it’s a pain to set up. here’s how I did it:

  1. That was incredibly useful – thank you so much! =D

    You might consider initialising _current_song_id to -1 on instantiation though rather than leaving it as the implicit default of 0. Took me a few mins to trace it through and figure out why track 0 would never play (due to first ‘if’ statement in Play method).

    Like

Leave a comment