Scripting

21. Scripting

FMOD Studio's scripting feature lets you control Studio and edit projects by using JavaScript. Commands can be entered into the built-in scripting interface, written into files that can be loaded either when FMOD Studio is launched or while it is running, or via TCP/IP connection.

Scripts can be used to perform a variety of tasks:

  • Custom keyboard shortcuts: Write scripts to automate tasks you perform regularly in FMOD Studio.
  • Level editor integrations: Support creating new events or modifying existing events, directly within your level editor.
  • Cutscene editor integration: Sync the timeline between an animation editor and an FMOD event.
  • DAW integration: Export audio directly into FMOD Studio.
  • Batch processing: Create lots of events quickly by writing a script that loads a CSV file as the specification.
  • Advanced auditioning: Audition complex events by writing custom UI to drive parameters.

For more information about the scripting API, see the Scripting API Reference.

21.1 Script Files

Script files are automatically evaluated every time you load a project. FMOD Studio reads scripts from any files with the .js extension in the following locations:

  • System scripts directory:
    • Windows: %localappdata%/FMOD Studio/Scripts
    • Mac: ~/Library/Application Support/FMOD Studio/Scripts
  • Built-in scripts directory:
    • Windows: %fmod_install_directory%/Scripts
    • Mac: %fmod_bundle%/Scripts
  • Project scripts directory:
    • %project_root_directory%/Scripts

After adding or altering a script in one of these directories, you can select "Scripts > Reload" to use the newly updated script.

Some example scripts are included with FMOD Studio, and can be found in the built-in scripts directory.

21.2 Terminal interface

Studio also supports evaluating script code in a REPL style interface. This can be used to control Studio remotely from an external application (e.g. your level editor).

Communicating with Studio is done via a TCP/IP connection over port 3663. Any data transmitted to Studio will be interpreted as JavaScript encoded as UTF-8. Any data received by clients should be interpreted as UTF-8 strings, representing the evaluated code.

Commands can also be run using the built-in terminal interface. The console window can be opened by selecting "Window > Console" in the menu bar or by using the shortcut key combination Control+0 on Windows or ⌘+0 on Mac. This opens the console window, which contains the terminal tab. You can type commands directly into this:

console

21.3 Scripting examples

21.3.1 Creating and Deleting an Event

Using the managed object API, along with the studio.project.create(entityName) function, allows you to create objects within the project. For example, to create a new event in the root folder of the project:

    myEvent = studio.project.create("Event");
    myEvent.name = "My New Event";
    myEvent.folder = studio.project.workspace.masterEventFolder;

And to delete the newly created event:

    myEvent.deleteObject();

21.3.2 Checking the Tool Version

Studio provides the studio.version script object which allows you to check the tool version. It is good practice to check the tool version before installing script functionality. For example:

    if(studio.version.productVersion == 1 && studio.version.majorVersion >= 7) {
        // add menu bar items
    }

21.3.3 Modifying the Global Context

When Studio loads script within your Javascript files, it executes within its own scope. This means that if you declare a function called foo(), you will still not be able to call foo() directly from the shell interface. To register functions that can be called globally, you must attach them to the global context. For example:

    var global = this;
    global.foo = function() { alert("Hello!"); }

21.3.4 Custom Menus

    studio.menu.addMenuItem({
        name: "Greetings\\Say Hello",
        execute: function() { alert("Hello"); },
        keySequence: "Ctrl+H",
    });

    studio.menu.addMenuItem({
        name: "Greetings\\Say Bye",
        execute: function() { alert("Bye"); },
        isEnabled: false,
    });

    studio.menu.addMenuItem({ name: "Greetings\\----" }); // use all '-' characters to create a separator

    studio.menu.addMenuItem({
        name: "Greetings\\Say Checked",
        execute: function() { alert("Checked toggled"); this.isChecked = !this.isChecked; },
        isChecked: true,
    });

    studio.menu.addMenuItem({
        name: "Greetings\\Advanced\\Say what's Selected",
        execute: function() { alert(studio.window.browserCurrent()); },
        isVisible: function() { return studio.window.browserCurrent(); }
    });

    studio.menu.addMenuItem({
        name: "Greetings\\Dynamic Submenu",
        subMenuItems: function() {
            var items = [];
            for(var i=0; i < 4; i++) {
                items.push({
                    name: "#" + i,
                    execute: function() { alert("Well hello there " + this.name); },
                });
            }
            return items;
        },
    });

21.3.5 Custom Save & Build Menu Item

    studio.menu.addMenuItem({ name: "Save and Build", execute: function buildAndCopy() {
        studio.project.save();
        studio.project.build();
        alert("Save and Build complete!");
    }});

21.3.6 Setting Master Volume

    var masterBus = studio.project.workspace.mixer.masterBus;
    masterBus.volume = -2;

21.3.7 Selecting an Event and Setting Timeline Position

    var eventId = "{aabe5118-c144-4dc3-839a-ff52a2b49162}";
    var timelinePos = 2.3;

    var event = studio.project.lookup(eventId);
    if(event) {
        studio.project.open(event);
        event.timeline.setCursorPosition(timelinePos);
        alert("Opened and scrubbed: " + event.name);
    }
    else {
        alert("Could not find event: " + eventId);
    }

21.3.8 Adding a New Group Track to the Selected Event

    studio.menu.addMenuItem({ name: "Add Group Track",
        isEnabled: function() { var event = studio.window.browserCurrent(); return event && event.isOfExactType("Event"); },
        execute: function() {
            var trackName = studio.system.getText("Name of new group track:", "New Track");
            if(trackName) {
                var event = studio.window.browserCurrent();
                var track = studio.project.create("GroupTrack");
                track.mixerGroup.output = event.mixer.masterBus;
                track.mixerGroup.name = trackName;
                event.relationships.groupTracks.add(track);
            }
        },
    });

21.3.9 Sort Audio Files After They're Imported

    function postAudioFileImported( audioFile ) {
        // tries to put 'abc_sound.wav' into the 'ABC' folder if it exists
        var fileName = audioFile.audioFilePath.split('/').pop();
        var prefix = fileName.substr(0, 3).toUpperCase();
        console.log(fileName + " has the prefix " + prefix);

        if(audioFile.container.id != studio.project.workspace.audioBinManager.masterAudioBinFolder.id) {
            console.log("The file isn't in the root folder -- user dragged to a specific folder so we can skip this.");
            return;
        }

        var items = studio.project.workspace.audioBinManager.masterAudioBinFolder.items;
        for(var i=0; i < items.length; i++) {
            if(items[i].isOfType("AudioBinFolder") && items[i].name === prefix) {
                audioFile.container = items[i];
                console.log("Moving" + fileName + " to " + items[i].name);
                return;
            } 
        }
    }
    studio.project.audioFileImported.connect(postAudioFileImported);