Monogame on Mac + FMod

I’ve been using Monogame for about 4 years now, since I want to create a game with my own engine and not use Unity, like many people around here I guess. I love Monogame. I want to start using my Macbook M1 to compile my project and I’m just trying to make a brand new clean project right now. I can compile a blank new monogame project on my Macbook M1, finally (it’s been an adventure to make it work).

I now want to install FMOD to this new project.

I need help!

I downloaded FMOD for Mac, but examples seems to be tied to xcode :frowning:

Anyone can help to include libraries and include files to a blank MG project? I want to use basic FMOD Studio API, that’s what I was using on my windows project (that has several hundreds of hours of code - I don’t want to lose it all), it was a UWP project, now I want to port it to a DesktopGL project and use my macbook for development.

Any help will be greatly appreciated!

Thanks :slight_smile:
Styves

Ok, I figured out how to use FMOD with Visual Studio 2019 for Mac in my Monogame project and I’m very happy that everything finally works! If anybody struggles with its initial M1 + VSS2019 + DOTNET setup or FMOD setup in that configuration, feel free to ping me!

Since I am playing around with audio, I would like to know which resources and tutorials you find useful for FMOD? FMOD is written in C++ right? So since I don’t know C++ for me it is difficult to understand how to use / consume it from the MonoGame C# side and also to find out how FMOD is intended to being used in general if that makes sense?
It would be nice to have the basic steps lined out and the reasoning behind it how to play an audio file with FMOD in a MonoGame application.

I did not find any useful resources for FMOD, that’s why I struggled :wink: I had to figure it out by myself, I’m glad to share! Here are the steps, sorry if some of them are really obvious, I prefer more than not enough details…

How I setup FMOD to work with my monogame project (tested with both VS2019 and VS2022 preview for Mac)

  1. Download the FMOD engine for Mac.

  2. Copy the files in the “inc” and “lib” folders from the API Programmers API/api/studio and API Programmers API/api/core to your project. I created a “fmod” folder and put them all in it.

  3. I right clicked on the 4 .dylib files (release and debug), Quick Properties, Copy to output directory. But my application did not seems to find the libs when I ran it, I ended up copying the libs in the “/usr/local/lib” folder to make it work. Had to create that “lib” folder on my mac, it was not existing. Not sure if it’s the right way to do it to be fair, but I struggled a lot to make it work and when this worked, it was my aha moment and I was satisfied enough with the solution :stuck_out_tongue:

  4. Use the FMOD Studio application to create a bank (plenty of tutorials on youtube) : in short for simple stuff like playing background music and sound, I import assets (wav files for example) in the fmod studio project, then I create an Event for it that I name for example “bgmusic”. I organize my events in folders like “MUSIC”, “SFX”, “AMBIANCE”. Then build the bank in file/build. It will create 2 files : Master.bank and Master.strings.bank. Copy those 2 files in your project, I put them in a Content folder and FMOD Studio creates automatically a subfolder with the platform, in my case Desktop, so I get a Content/Desktop/ folder containing the 2 files. I right clicked on those 2 files in VS, Quick properties and Copy to output directory.

The project should build successfuly at that point.

To use FMOD in my project, I coded a wrapper class to make it easier to work with the FMOD API. I grabbed snippets of code here and there and added my own things in it, it’s not very clean but if it can help you to figure out how to start, here it is (change the folder according to your content folder structure + your FMOD Studio project events structure obviously) :

public class SfxEvent
{
    public FMOD.Studio.EventDescription desc;
    public FMOD.Studio.EventInstance instance;
    public bool isContiunous; //if set to true, the sound will not be released immediatly after start
}

public class AudioEngine
{
    FMOD.System _fmodLowLevelSystem;
    FMOD.Studio.System _fmodSystem;

    //Music and Ambiance
    private string _currentMusicName = "";
    private FMOD.Studio.EventDescription _musicDescription;
    private FMOD.Studio.EventInstance _musicInstance;

    private string _currentAmbianceName = "";
    private FMOD.Studio.EventDescription _ambianceDescription;
    private FMOD.Studio.EventInstance _ambianceInstance;

    //Sounds Bank
    private Dictionary<string, SfxEvent> _sfxDict;

    //Buses
    private Dictionary<string, FMOD.Studio.Bus> _busDict;

    public string currentMusicName { get { return _currentMusicName; } }
    public string currentAmbianceName { get { return _currentAmbianceName; } }

    FMOD.Studio.EventInstance _snapshotInstance;
    FMOD.Studio.EventDescription _snapshotDesc;

    public void Initialize()
    {
        ErrorCheck(FMOD.Studio.System.create(out _fmodSystem)); 
        ErrorCheck(_fmodSystem.initialize(512, FMOD.Studio.INITFLAGS.NORMAL, FMOD.INITFLAGS.PREFER_DOLBY_DOWNMIX, (IntPtr)0));
        ErrorCheck(_fmodSystem.getCoreSystem(out _fmodLowLevelSystem));

        //Load Default Sound Bank - change the name to whatever your bank is named, without extensions
        LoadBank("Master");

        _sfxDict = new Dictionary<string, SfxEvent>();
        _busDict = new Dictionary<string, FMOD.Studio.Bus>();
    }

    public void LoadBus(string busName)
    {
        FMOD.Studio.Bus bus;
        _fmodSystem.getBus("bus:/" + busName, out bus);
        _busDict.Add(busName, bus);
    }

    public void SetBusVolume(string busName, float volume)
    {
        if(_busDict.ContainsKey(busName))
        {
            _busDict[busName].setVolume(volume);
        }
    }

    public float GetBusVolume(string busName)
    {
        float vol = -1f;
        float finalVol;

        if (_busDict.ContainsKey(busName))
        {
            _busDict[busName].getVolume(out vol, out finalVol);
        }

        return vol;
    }

    public void LoadBank(string bankFileName)
    {
        FMOD.Studio.Bank masterBank;
        ErrorCheck(_fmodSystem.loadBankFile("Content/Desktop/" + bankFileName + ".bank", FMOD.Studio.LOAD_BANK_FLAGS.NORMAL, out masterBank));

        FMOD.Studio.Bank stringsBank;
        ErrorCheck(_fmodSystem.loadBankFile("Content/Desktop/" + bankFileName + ".strings.bank", FMOD.Studio.LOAD_BANK_FLAGS.NORMAL, out stringsBank));
   }

    public void LoadAndApplySnapshot(string snapshotName)
    {
       _snapshotInstance.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
        ErrorCheck(_fmodSystem.getEvent("snapshot:/" + snapshotName, out _snapshotDesc));
        _snapshotDesc.createInstance(out _snapshotInstance);
        _snapshotInstance.start();
    }

    public void LoadSound(string sndName, bool isLooping = false)
    {
        SfxEvent sfx = new SfxEvent();
        sfx.isContiunous = isLooping;
        ErrorCheck(_fmodSystem.getEvent("event:/"+ sndName, out sfx.desc));

        // Start loading sample data and keep it in memory
        ErrorCheck(sfx.desc.loadSampleData());

        _sfxDict.Add(sndName, sfx);
    }

    public void PlaySound(string sndName, float volume = -1000.0f, float pitch = -1000.0f)
    {
        if (_sfxDict.ContainsKey(sndName))
        {
            FMOD.Studio.EventInstance instance;
            ErrorCheck(_sfxDict[sndName].desc.createInstance(out instance));

            if (pitch != -1000.0f) //if not specified, leave fmod studio default value and don't apply any pitch
                instance.setPitch(pitch);

            if(volume != -1000.0f) //if not specified, leave fmod studio default value and don't apply any volume
                instance.setVolume(volume);

            instance.start();

            if (!_sfxDict[sndName].isContiunous)
                instance.release();

            _sfxDict[sndName].instance = instance;
        }
    }

    public void PlayMusic(string trackName)
    {
        if (_currentMusicName != trackName)
        {
            ErrorCheck(_fmodSystem.getEvent("event:/MUSIC/" + trackName, out _musicDescription));
            ErrorCheck(_musicDescription.createInstance(out _musicInstance));
            _currentMusicName = trackName;
        }
        ErrorCheck(_musicInstance.start());
    }

    public void PlayAmbiance(string ambianceName)
    {
        if (_currentAmbianceName != ambianceName)
        {
            ErrorCheck(_fmodSystem.getEvent("event:/AMBIANCE/" + ambianceName, out _ambianceDescription));
            ErrorCheck(_ambianceDescription.createInstance(out _ambianceInstance));
            _currentAmbianceName = ambianceName;
        }
        ErrorCheck(_ambianceInstance.start());
    }

    public void StopMusic(bool fadeOut = false)
    {
        if (_currentMusicName != "")
        {
            _musicInstance.stop(fadeOut ? FMOD.Studio.STOP_MODE.ALLOWFADEOUT : FMOD.Studio.STOP_MODE.IMMEDIATE);
            _musicInstance.release();
            _currentMusicName = "";
        }
    }

    public void StopAmbiance(bool fadeOut = false)
    {
        if (_currentAmbianceName != "")
        {
            ErrorCheck(_ambianceInstance.stop(fadeOut ? FMOD.Studio.STOP_MODE.ALLOWFADEOUT : FMOD.Studio.STOP_MODE.IMMEDIATE));
            //ErrorCheck(ambianceInstance.release());
            _currentAmbianceName = "";
        }
    }

    public void PauseMusic(bool pause)
    {
        if(_currentMusicName != "")
        {
            ErrorCheck(_musicInstance.setPaused(pause));
        }
    }

    public void PauseAmbiance(bool pause)
    {
        if (_currentAmbianceName != "")
        {
            ErrorCheck(_ambianceInstance.setPaused(pause));
        }
    }

    public void Update()
    {
        ErrorCheck(_fmodSystem.update());
    }

    public int ErrorCheck(FMOD.RESULT result)
    {
        if (result != FMOD.RESULT.OK)
        {
            Console.WriteLine(result.ToString() + " | Stack Trace : " + Environment.StackTrace);
            return 1;
        }
        return 0;
    }
}

I hope it can help a little to get you going!

Happy coding :slight_smile:

1 Like

Thanks for sharing! What are the differences when developing on Windows? So far I logged in to the website and downloaded the FMOD Engine for Windows. I copied over the api (C:\Program Files (x86)\FMOD SoundSystem\FMOD Studio API Windows\api) folder into a newly created folder inside my solution named fmod. So now I have everything I need from there I guess?
Since on windows I think now I should use *.dll files instead of *.dylib files, right? I think those ones might be right?

<fmod-api-folder> = 
C:\Program Files (x86)\FMOD SoundSystem\FMOD Studio API Windows\api\
...
<fmod-api-folder>\core\lib\x64\fmod.dll
<fmod-api-folder>\core\lib\x64\fmodL.dll
<fmod-api-folder>\studio\lib\x64\fmodstudio.dll
<fmod-api-folder>\studio\lib\x64\fmodstudioL.dll

From that point on I don’t understand how to initialize FMOD properly / the right way. I think I have to set the dlls to copy to output directory maybe and then initialize FMOD. But how the initalization works I have no idea. Maybe it is all in the FMOD docs and I should read it there?

I assune I have to call some routine or more routines from one or more of those dlls to be able to use FMOD from my MonoGame application?

Found this info on stackoverflow here Using FMOD for C#? - Stack Overflow

Fmod is written in unmanaged C++ so you cannot reference it directly from a .Net application. There is a c# wrapper to the fmodex.dll in the fmod package under a directory called "fmod_wrapper" if I am not mistaken that you can add to your project and which will take care of making the P/Invoking for you.

There is also this here mentioned on stackoverflow https://github.com/madrang/FmodSharp

That’s correct, I was using FMOD in a Windows MG project before and you have to use the DLL files instead of the dylib.

To initialize FMOD, the way I use it:

Grab my code above, modify according to your project and include it in your project, putting it in a new .cs file and wrapping the code in a namespace block
namespace whatever_namespace_your_project_has { ...the classes above... }

In your Game1 file, add
private AudioEngine _audio;

In your Game1 Initialize function, add
_audio = new AudioEngine();
_audio.Initialize();

In your Game1 LoadContent function, add
_audio.PlayMusic("bgmusic"); //replace bgmusic by whatever Event name you specified in FMOD Studio for this audio file. Be aware that in my class I add automatically a MUSIC/ prefix because I put my my music files in a MUSIC folder in my FMOD Studio project.

In your Game1 Update() function, add
_audio.Update();

You should be all set!

1 Like

About the last part of your message for the C# wrapper, I don’t know, I was using the Windows 10 UWP API files provided by FMOD and it was working well on my Windows C# Monogame project…

Cool stuff I will try it out and get back here and report if I got it working :+1:

1 Like

I added the classes into a new file like you said and added the code to the Game.cs. I verified that the *.dll files get copied to the output folder. They got copied here:

.\FMODMG.WindowsDX\bin\Debug\netcoreapp3.1\fmod\api\studio\lib\x64\fmodstudio.dll
.\FMODMG.WindowsDX\bin\Debug\netcoreapp3.1\fmod\api\studio\lib\x64\fmodstudioL.dll
...
.\FMODMG\FMODMG.WindowsDX\bin\Debug\netcoreapp3.1\fmod\api\core\lib\x64\fmod.dll
.\FMODMG\FMODMG.WindowsDX\bin\Debug\netcoreapp3.1\fmod\api\core\lib\x64\fmodL.dll

.\FMODMG.WindowsDX\bin\Debug\netcoreapp3.1\Content\Desktop\Master.bank
.\FMODMG.WindowsDX\bin\Debug\netcoreapp3.1\Content\Desktop\Master.strings.bank

This is what I have now…

I get an error on the initialization part:

Unable to load DLL 'fmodstudio' or one of its dependencies: The specified module could not be found. (0x8007007E)

Should I change something here?
I wonder how to tell my app where it should look for the fmodstudio.dll since it can’t find it?

Maybe take a look here : http://in.fmod.com/resources/documentation-api?version=2.01&page=platforms-win.html

I guess I forgot about the .lib files that you have to link in your project :frowning:

I think this video explains it step by step : Linking FMOD Library In Visual Studio - Project Settings - YouTube

Hope it will help!

I figured I will need to learn more about using native C/C++ libraries from managed code / C#. Or a step by step guide / tutorial because of too many questions on this topic.
From what I read and if I understand correctly there are different possibilities to load libraries?! Any breakdown on the loading and using native libraries topic would be helpful. And if there are good / recommended articles or docs for this.