FMOD in Node.js implementation help?

Hello! I am working with a fairly large team on a game made in Node.js and the Phaser 3 framework. There’s a handful of audio people, but there is not any actual audio / audio infrastructure in place yet. I think that it would be helpful to use FMOD for organization / collaboration / dynamic audio stuff, so I want to implement it!

I’m fairly experienced with FMOD, but am pretty novice programmer when it comes to Node. I downloaded the HTML5 FMOD API, and have Node installed. I generally understand how to navigate it, as well as the the game environment itself. Would anyone be able to describe how to go about implementing FMOD in this environment? I’ve heard that a solution might be to create a custom node module and install FMOD to it, but, if so, I have no idea what that actually looks like.

Thank you all in advance, and I can provide any more information as needed!

Apologies for the delayed response.

As far as the Phaser 3 side of things goes, implementation should be fairly simple, and in line with our HTML5 examples - the main thing is to ensure that your FMOD System is updating appropriately from Phaser 3.

Below are a JS file and HTML file that combine our HTML5 Studio example code with Phaser 3’s Hello World tutorial code to play a gunshot sound every time the Phaser 3 logo collides with the world bounds:

phaser3Example.js
var FMOD = {};                          // FMOD global object which must be declared to enable 'main' and 'preRun' and then call the constructor function.
FMOD['preRun'] = prerun;                // Will be called before FMOD runs, but after the Emscripten runtime has initialized
FMOD['onRuntimeInitialized'] = main;    // Called when the Emscripten runtime has initialized
FMOD['INITIAL_MEMORY'] = 64*1024*1024;  // (ASM.JS ONLY) FMOD Heap defaults to 16mb which is enough for this demo, but set it differently here for demonstration (64mb)
FMODModule(FMOD);                       // Calling the constructor function with our object

var gSystem;                            // Global 'System' object which has the Studio API functions.
var gSystemCore;                        // Global 'SystemCore' object which has the Core API functions.
var eventDescription = {};

// Simple error checking function for all FMOD return values
function CHECK_RESULT(result)
{
    if (result != FMOD.OK)
    {
        var msg = "Error!!! '" + FMOD.ErrorString(result) + "'";

        alert(msg);

        throw msg;
    }
}

// Helper function to preload FMOD bank files. Will be called before FMOD runs, but after the Emscripten runtime has initialized
function prerun()
{
    var fileUrl = "/banks/";
    var fileName;
    var folderName = "/";
    var canRead = true;
    var canWrite = false;

    fileName = [
        "Master.bank",
        "Master.strings.bank",
        "SFX.bank"
    ];

    for (var count = 0; count < fileName.length; count++)
    {
        FMOD.FS_createPreloadedFile(folderName, fileName[count], fileUrl + fileName[count], canRead, canWrite);
    }
}

// Called when the Emscripten runtime has initialized
function main()
{
    // A temporary empty object to hold our system
    var outval = {};
    var result;

    console.log("Creating FMOD System object\n");

    // Create the system and check the result
    result = FMOD.Studio_System_Create(outval);
    CHECK_RESULT(result);

    console.log("grabbing system object from temporary and storing it\n");

    // Take out our System object
    gSystem = outval.val;

    result = gSystem.getCoreSystem(outval);
    CHECK_RESULT(result);

    gSystemCore = outval.val;

    // Optional.  Setting DSP Buffer size can affect latency and stability.
    // Processing is currently done in the main thread so anything lower than 2048 samples can cause stuttering on some devices.
    console.log("set DSP Buffer size.\n");
    result = gSystemCore.setDSPBufferSize(2048, 2);
    CHECK_RESULT(result);

    // Optional.  Set sample rate of mixer to be the same as the OS output rate.
    // This can save CPU time and latency by avoiding the automatic insertion of a resampler at the output stage.
    console.log("Set mixer sample rate");
    result = gSystemCore.getDriverInfo(0, null, null, outval, null, null);
    CHECK_RESULT(result);
    result = gSystemCore.setSoftwareFormat(outval.val, FMOD.SPEAKERMODE_DEFAULT, 0)
    CHECK_RESULT(result);

    console.log("initialize FMOD\n");

    // 1024 virtual channels
    result = gSystem.initialize(1024, FMOD.STUDIO_INIT_NORMAL, FMOD.INIT_NORMAL, null);
    CHECK_RESULT(result);

    // Starting up your typical JavaScript application loop
    console.log("initialize Application\n");

    initApplication();

    console.log("Start game loop\n");

    // run phaser 3 code
    Phaser3Main();

    return FMOD.OK;
}

// Called from main, loads FMOD banks and preps an event description and its sample data
function initApplication()
{
    console.log("Loading events\n");

    loadBank("Master.bank");
    loadBank("Master.strings.bank");
    loadBank("SFX.bank");
    // Get the event we want to play
    CHECK_RESULT( gSystem.getEvent("event:/Weapons/Pistol", eventDescription) );

    // Start loading event sample data and keep it in memory
    CHECK_RESULT( eventDescription.val.loadSampleData() );

}

// Helper function to load a bank by name
function loadBank(name)
{
    var bankhandle = {};
    CHECK_RESULT( gSystem.loadBankFile("/" + name, FMOD.STUDIO_LOAD_BANK_NORMAL, bankhandle) );
}

function Phaser3Main()
{

    class Example extends Phaser.Scene
    {

        preload ()
        {
            this.load.setBaseURL('https://labs.phaser.io');

            this.load.image('sky', 'assets/skies/space3.png');
            this.load.image('logo', 'assets/sprites/phaser3-logo.png');
            this.load.image('red', 'assets/particles/red.png');
        }

        create ()
        {

            this.add.image(400, 300, 'sky');

            const particles = this.add.particles(0, 0, 'red', {
                speed: 100,
                scale: { start: 1, end: 0 },
                blendMode: 'ADD'
            });

            const logo = this.physics.add.image(400, 100, 'logo');

            logo.setVelocity(100, 200);
            logo.setBounce(1, 1);
            logo.body.setCollideWorldBounds(true);
            logo.body.onWorldBounds = true;
            
            particles.startFollow(logo);

            // listen for collision of logo with world bounds, and play gunshot sound effect
            this.physics.world.on(Phaser.Physics.Arcade.Events.WORLD_BOUNDS, function () {
                let out = {};
                eventDescription.val.createInstance(out);
                let instance = out.val;
                instance.start();
                instance.release();
            });
        }
        
        // update FMOD system
        update ()
        {
            let result = gSystem.update();
            CHECK_RESULT(result);
        }
    }

    const config = {
        type: Phaser.AUTO,
        width: 800,
        height: 600,
        scene: Example,
        physics: {
            default: 'arcade',
            arcade: {
                gravity: { y: 200 }
            }
        }
    };

    const game = new Phaser.Game(config);

}
index.html
<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser-arcade-physics.min.js"></script>
    <script type="text/javascript" src="fmodstudio.js"></script>
</head>
<body>
    <script type="text/javascript" src="phaser3Example.js"></script>
</body>
</html>

These will require the appropriate FMOD Studio and Phaser 3 js libs to be placed in the root directory of your web server, as well as the master, master strings, and SFX banks from our example to be placed at ./banks/.

As for the Node.js side of things, it’ll probably be a little more complex. You should be able to create a Node.js ECMAScript or CommonJS module for FMOD, but we don’t have any examples on hand that demonstrate how to do so. There are a few packages on npm that users have created that use various versions of FMOD that may serve as examples. If you do run into any specific issues, feel free to let me know.

That said, we have had other users ask about FMOD usage with web tech like Node.js and ASP.NET, and potentially providing examples of using FMOD with a Node.js module, so I’ve noted your interest internally.

Oh! Thank you so much! In the intervening week I was able to work through it by scouring forums / the HTML / JS documentation / the web examples provided. Eventually ended up getting it to work in the way you described, but I’ll definitely cross-check what I did with the examples provided to make sure I didn’t miss anything crucial. It wasn’t too bad, even with little JS experience!

I haven’t needed to interface really with anything Node-specific actually (currently everything is working? Banks are loaded and events are called and a parameters are changed and everything). But yes I would definitely advocate for a Node.js module! And I’ll update if I run into any problems I can’t figure out!

Thanks again for your comprehensive response! :smiley:

1 Like