FMOD Studio Scripting - Loop over all events and add Effect Chain to end of master track

Im trying to automate adding a custom Effect Chain to all the selected events in FMOD Studio 2.02.20 since from what i can tell you cant add effects all selected events in one go. Hence im trying to write a script that does this, however, im really struggling to find documentation on exactly how to do this.

Any advice on how this can be achieved?

1 Like

Okay so i have figured out how to add an effect chain preset to the master track of selected events. But something that would help massively with this would be to be able to check if the effect preset already exists on the event and then only add it if it isnt there.
How can this be done?

Here is the code for anybody who’d like to know:

var effectName = studio.system.getText("Name of Effect Chain:", "Effect Chain");
        
if(effectName) {
   var selectedEvents = studio.window.browserSelection();
   var presets = studio.project.model.EffectPreset.findInstances();

   presets.forEach(function(effect) {
      if(effect.name == effectName) {
         selectedEvents.forEach(function(selectedEvent) {
            selectedEvent.masterTrack.mixerGroup.effectChain.addEffect(effect);
         })
      }
   })
}
1 Like

I got there eventually despite struggling with the fact that i cant use a form of intellisense in Rider to code this :sweat_smile:

Here is the code solution for anybody looking to do this in the future:

var effectName = studio.system.getText("Name of Effect Chain:", "Effect Chain");
        
        if(effectName)
        {
            var selectedEvents = studio.window.browserSelection();
            var presets = studio.project.model.EffectPreset.findInstances();

            presets.forEach(function(effect)
            {
                if(effect.name == effectName)
                {
                    selectedEvents.forEach(function(selectedEvent)
                    {
                        var doesEffectExistOnEvent = false;
                        var eventEffects = selectedEvent.masterTrack.mixerGroup.effectChain.effects;
                        
                        eventEffects.forEach(function(eventEffect)
                        {
                            if(eventEffect.preset && eventEffect.preset.name == effectName)
                            {
                                doesEffectExistOnEvent = true;
                                console.log(selectedEvent.name + " already has the '" + effectName + "' Effect Chain assigned to it.");
                                return;
                            }
                        })
    
                        if(!doesEffectExistOnEvent)
                        {
                            selectedEvent.masterTrack.mixerGroup.effectChain.addEffect(effect)
                            console.log("Assigned '" + effectName + "' Effect Chain to event: " + selectedEvent.name);
                        }
                    })
                }
            })
        }
3 Likes

Hi, thank you for sharing the solution!

Not only did you implement the solution, but you also beat me to posting a solution! I tested your script on my end and it looks really great.

Please don’t hesitate to ask if you have any more questions.

1 Like

Hehe thanks, im quite proud of reaching the solution in the end :slight_smile:

@li_fmod could you point me to how to remove effects and/or effect chain presets from an event? (I’d like to create a similar tool to remove them after detecting duplicates)

1 Like

To remove an effect or effect chain, you could use studio.project.deleteObject (FMOD Studio | 24. Scripting API Reference | Project | project.deleteObject).

Here is an example script that I modify from your script:

studio.menu.addMenuItem({
    name: "User Scripts ...\\ Remove EffectChain",

    execute: function () {
                  
        var effectName = studio.system.getText("Name of Effect Chain:", "Effect Chain");
        
        if(effectName)
        {
            var selectedEvents = studio.window.browserSelection();
            var presets = studio.project.model.EffectPreset.findInstances();
            presets.forEach(function(effect)
            {               
                if(effect.name == effectName)
                {
                    selectedEvents.forEach(function(selectedEvent)
                    {
                        var eventEffects = selectedEvent.masterTrack.mixerGroup.effectChain.effects;

                        eventEffects.forEach(function(eventEffect, removeIndex)
                        {                           
                            if(eventEffect.preset && eventEffect.preset.name == effectName)
                            {
                                console.log(selectedEvent.name + " already has the '" + effectName + "' Effect Chain assigned to it.");
                                studio.project.deleteObject(selectedEvent.masterTrack.mixerGroup.effectChain.effects[removeIndex]);  //remove the effect
                                return;
                            }
                        })
                    })
                }
            })
        }
    }
})

Hope it helps, let me know if you have any questions.

1 Like

Awesome, thats very helpful indeed. This is a great place to start! Thanks very much @li_fmod :slight_smile:

@li_fmod Something that has also come up is that is that studio.project.model.EffectPreset.findInstances(); is returning both effect preset parameters and effect chains. It also returns if they are folders.

How can i distinguish between all of these types?

A solution may be to use the effect property to filter out the desired object

For example, below is a scripts that will iterate through all effects from the presets and add the desired ones to the selected master track:

studio.menu.addMenuItem({
    name: "User Scripts ...\\ Add EffectChains from preset",

    execute: function () {
                  
            var selectedEvents = studio.window.browserSelection();
            var presets = studio.project.model.EffectPreset.findInstances();

            presets.forEach(function(effect)
            {
                if(effect.effect == "(ManagedObject:EffectChain)")
                {
                    selectedEvents.forEach(function(selectedEvent)
                    {
                        selectedEvent.masterTrack.mixerGroup.effectChain.addEffect(effect)  //find all effect chains within presets and add them to selected master track
                    })
                }
            })
        
    }
})

I would also recommend you to use the dump function (FMOD Studio | 24. Scripting API Reference | Project.ManagedObject | ManagedObject.dump) to see if you can find any useful information or properties that might help you.

Please let me know if you have any questions.

Unfortunately that didnt work as preset parameters also return “effect: (ManagedObject:EffectChain)” which seems a bit weird to me.

Ive gone through the dump and cant seem to find anything that indicates a distinction between parameters and effect chains. But i may be missing something.

Could you please further elaborate on the desired behaviour?

Absolutely, I wish for my script to add a named EffectChain Preset to the selected tracks. Currently when looping over the presets that are returned when calling “studio.project.model.EffectPreset.findInstances()” this returns not only effect chain presets, but also the folders and parameters with the same name.

The example i am currently using to test this is that i have an effect chain named “Reverb,” as well as a parameter named the same. The effect chain preset also lives under a folder named “Reverb” too - and all 3 of these are returned when executing this code:

var presets = studio.project.model.EffectPreset.findInstances();
presets.forEach(function(effect) {               
   if(effect.name == effectName){
   }
}

Thank you for the clarification.

Could you please add an additional specific check within the iteration: effect.effect === "(ManagedObject:EffectChain)"

This check will ensure that the search is limited to effect presets whose type is identified as an effect chain.

For example:

var presets = studio.project.model.EffectPreset.findInstances();
presets.forEach(function(effect){
    if(effect.name == effectName && effect.effect == "(ManagedObject:EffectChain)"){
    }
}

Would it be possible to further elaborate on what parameters refers to? If you are referring to parameter preset, it can only be returned by studio.project.model.ParameterPreset.findInstances()

Let me know if the issue persists, and please don’t hesitate to ask questions.

Okay so this may actually be a bug in Studio v2.02.20 then cause when i call this it returns parameters and folders as well:

var presets = studio.project.model.EffectPreset.findInstances();
presets.forEach(function (preset){
if(preset.name == "Reverb" && preset.effect == "(ManagedObject:EffectChain)"){
 preset.dump();
 console.log("---------");
}
})

These are the only presets and effects chains/folders called “Reverb” in my project, as you can see in my presets window:

As you can see, There is 1 parameter, 1 Effect Chain, and 1 folder that the effect chain is a child of that are all called Reverb. I would expect with what you suggested above that this code should only return the Effect Chain called Reverb.

However this is the log that gets returned:

(ManagedObject:EffectPreset):
id: "{f6f31d09-2d69-41d1-a6dd-15c417e0498a}",
entity: "EffectPreset",
isValid: false,
relationships: (ManagedRelationshipMap:EffectPreset),
properties: (ManagedPropertyMap:EffectPreset),
isOfType: <function>,
isOfExactType: <function>,
note: undefined,
color: "Default",
name: "Reverb",
folder: null,
effect: (ManagedObject:EffectChain),
proxies: [],
dump: <function>,
document: <function>,
---------
(ManagedObject:EffectPreset):
id: "{df6a7ded-57f7-4a4b-97e5-c22e60c64b03}",
entity: "EffectPreset",
isValid: true,
relationships: (ManagedRelationshipMap:EffectPreset),
properties: (ManagedPropertyMap:EffectPreset),
isOfType: <function>,
isOfExactType: <function>,
note: undefined,
color: "Default",
name: "Reverb",
folder: (ManagedObject:EffectPresetFolder),
effect: (ManagedObject:EffectChain),
proxies: [(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect),(ManagedObject:ProxyEffect)],
dump: <function>,
document: <function>,
---------
(ManagedObject:EffectPreset):
id: "{6d5e601a-6549-4b2e-8ad7-6d4a238f307b}",
entity: "EffectPreset",
isValid: false,
relationships: (ManagedRelationshipMap:EffectPreset),
properties: (ManagedPropertyMap:EffectPreset),
isOfType: <function>,
isOfExactType: <function>,
note: undefined,
color: "Default",
name: "Reverb",
folder: null,
effect: (ManagedObject:EffectChain),
proxies: [],
dump: <function>,
document: <function>,
---------

Thank you for sharing the code and logs.

I think I have identified the issue in the logs. The 3 returned Reverb objects from the logs are all EffectChain presets, however, they are not all valid. You can confirm this by checking the isValid property FMOD Studio | 24. Scripting API Reference | Project.ManagedObject | ManagedObject.isValid

Could you please try adding an additional check : preset.isValid == true to see if the issue persists?

var presets = studio.project.model.EffectPreset.findInstances();
presets.forEach(function (preset){
if(preset.name == "Reverb" && preset.effect == "(ManagedObject:EffectChain)" && preset.isValid == true){
preset.dump();
console.log("---------");
}
})

Alternatively, if you close, reopen FMOD studio, there should be no more invalid objects in the log.

Oh i get it now - apologies, my assumptions were off but it so happened that my methodology led me to believe the parameter and folder were being considered to be effect chains, whereas actually what it was was that the Reverb effect chains that i had removed from the event were still hanging around but werent valid as i assume they hadnt been cleaned up or something :slight_smile:

Adding the isValid check fixes this issue! Thanks so much for this - it would have taken me ages to figure that out on my own. That should be everything for now but ill be in touch if anything else comes up!

1 Like