I’m just wondering what the best way to setup ambient one shots is? As in looping events which trigger sounds from a multi instrument at random intervals.
My event is setup with an action sheet and a multi, inside the multi there’s a silence instrument with a random time and another multi for the ambient one shots, e.g fence wind rattle or wood creaks etc
In our game most of the audio lives in prefabs and sometimes there can be dozens of the same prefab next to each other like the fences. I obviously want to limit the instances for performance reasons but I’m getting some undesirable results. I’ve experimented with the spawn stealing settings but I never get the result I want, especially if there’s many objects of the same type next to each other, I’ll run past a row of fences and the spawn stealing tries to steal from the furthest (the delay at the start of the event never reaches the sound) or the max distance stops the instance so I never get a consistently moving ambience.
I’d expect to have a soundscape with randomly triggered ambient sounds from all directions, not just the closest emitters to me and also the feeling of running into the sounds instead of them triggering while I walk past and then always hearing them behind me.
We’ve got StopEventsOutsideMaxDistance checked maybe that’d causing issues but it’s better for performance.
If you could offer some best practises when dealing with ambient one shots it’d be much appreciated!
There’s no one “best” way to do this. There are many different ways in which it could be done, and since every game has different requirements, different ways are suited to different games.
That being said, one of the most popular ways is to use a scatterer instrument. At random (but customizable) intervals, a scatterer selects an entry from its playlist, and spawns an instance of that entry. Each such spawned instance is assigned a random position offset within a certain range of the parent event instance, which means that a single scatterer event instance can spawn sounds over a wide area around that event instance.
That being said, while scatterer instruments are powerful, they are also more complex than other instrument types and may take some getting used to, so I reccomend you thoroughly read our documentation on them before using them in your game.
I can’t be totally sure from your description, but I suspect you’re experimenting with event- and bus-level instance limiting. Are you aware that the FMOD Engine’s voice virtualization system always automatically handles this type of resource management for you, with no need to touch the event- and bus-level max instance properties, and that its impact on your game’s audio output is usually so subtle as to be unnoticeable?
While the virtual voice system is complex, its most basic behavior is easy to grasp: If a channel/voice’s volume would be attenuated all the way to silence, that channel/voice is automatically “virtualized.” A virtualized voice isn’t processed and isn’t included in the mix, and so it consumes no resources. Virtual voices’ volume properties are still tracked, however, and if they ever return to audible volume levels, they automatically become non-virtual again and resume playing.
The great thing about the virtual voice system is that, because it virtualizes voices when they’re inaudibly quiet, you should never actually hear voices popping in and out - and yet you never have to worry about the resource cost of all those silent voices, nor about silenced voices failing to start again when they should resume being audible.
(That being said, if you play an extremely large number of voices that are loud enough to be audible, you will overwhelm the maximum number of non-virtual voices that your game can play, and the virtual voice system will be forced to virtualize some of the loud-enough-to-be-audible voices as well. Even in that case, however, it will virtualize the quietest voices first, to make the effect as subtle as possible.)
Thanks so much for the information! and apologies for the delayed response.
The virtualisation system looks very powerful and would be perfect if there were’nt so many instances in our game.
The problem my end is the developer would like most audio to live in prefabs so in makes building levels quicker (plus part of the game is procedurally generated). I’m a big fan of trying to keep most of the environment 3D but currently I’m struggling with how to get the behaviour I want.
If we take this scenario for example, we’re in an area with a whole ton of fences
The fence prefab here has a quiet wind/shake sound on it, as I run around I’d like to occasionally hear one shake in the wind, the event has random delay and a random container, so either a scatter instrument with no distance or something else … there’s a problem here though, at default settings every instance within max distance plays, we’ve got a voice count of like 30 possibly which is overkill for just one ambient event. If I set unity to stop events outside max distance and put a cap on the max instances then with all of the stealing options the instances cut out and only play the two nearest. Obviously in a setup like this there’s going to possibly hundreds of virtualised instances if we choose to virtualise them.
So I’m not sure how to approach it really, is there a solution that doesn’t require more code and can be achieved in FMOD or will we have to script something?
I had an implementation idea for a river soundscape, which I wanted to come from multiple sources without duplicating independent event instances. This may (or may not) fit your needs here.
The idea was to play a unique, non-spatialised soundscape, run it through a transceiver, and then spamming dummy events, persistants, spatialised and virtualised, containing only the transceiver receptor.
This way you can have global control over the entire soundscape, and the sound would always come from the right direction.
Alcibiade’s solution is a good one, and I imagine it would potentially work for your use-case.
By using a single event instance to generate the ambiance and then routing that instance’s output to the dummy event instances in all of your fence prefabs, you’d be able to keep the voice count as low as if you had a single event instance, and thus avoid the need for virtualization; and as long as you put the spatialization in the dummy event rather than the ambiance event, the fences’ would all play in sync, so it’d nearly always sound like the nearest, largest fence was the one that was creaking.
You would need to create and play a single instance of the ambiance event, in addition to the instances of the dummy event attached to your fence prefabs, but that shouldn’t be any hard. You could even attach it to a prefab elsewhere in your game world if you like, since it won’t itself produce any audible output, meaning that its exact location in the world won’t be important as long as there’s never more than one instance of it playing at a time.
The trasmitter idea is solid, I tried it and it does work, all instances of the fence prefab are in sync and coupled with the max instance cap it produces a decent effect! although the sound sounds like it travels with you, when using it in an area with many prefabs stiched together or close to each other it has a similar effect as if you were to attach a sound to a dolly track or spline and move with it, very cool but not quite what I was going for.
The effect I’m looking to get is like a static scatter instrument but with fixed positions (in this case the prefab emitters), if I stand in the middle of a room and have a oneshot emitter on each side of me, each with a scatter instrument on it, there’d be a global polyphony so if a sound wanted to trigger it would have to wait for another instance to stop it’s current sound.
I tried to code up something today using callbacks, I feel like I’m close but the script doesn’t seem to stop the instance properly, perhaps you can take a look? maybe you can see what I’m going for here. I should note every sound in our game is spawned with a StudioEventEmitter and the one shot events are just in a scatter instrument.
FYI I’m not really a programmer so this may be messy or incorrect! Thanks for taking a look!
using FMOD;
using FMOD.Studio;
using FMODUnity;
using System;
using System.Collections.Generic;
using UnityEngine;
public class PolyphonyInfo {
public int CurrentPolyphony;
public int MaxPolyphony;
}
public class FMODInstancePolyphonyHandler : MonoBehaviour {
public EventReference[] AmbientReferences;
static Dictionary<EventDescription, PolyphonyInfo> m_Polyphony = new();
EVENT_CALLBACK m_Callback;
private void Awake() {
m_Polyphony.Clear();
m_Callback = new EVENT_CALLBACK(AmbienceCallback);
foreach (var reference in AmbientReferences) {
EventDescription description = RuntimeManager.GetEventDescription(reference);
description.setCallback(AmbienceCallback);
m_Polyphony.Add(description, new PolyphonyInfo());
RetrieveUserProperty(description);
}
}
private void OnDestroy() {
foreach (var reference in AmbientReferences) {
EventDescription description = RuntimeManager.GetEventDescription(reference);
description.setCallback(null);
}
m_Callback = null;
m_Polyphony.Clear();
}
private void RetrieveUserProperty(EventDescription description) {
RESULT userPropertyResult = description.getUserProperty("MaxPolyphony", out USER_PROPERTY property);
if (userPropertyResult == RESULT.OK) m_Polyphony[description].MaxPolyphony = (int)property.floatValue();
else UnityEngine.Debug.Log($"Could not find MaxPolyphony. Is user property set in FMOD Studio?");
}
[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
static RESULT AmbienceCallback(EVENT_CALLBACK_TYPE type, IntPtr _event, IntPtr parameters) {
if (_event == System.IntPtr.Zero) {
UnityEngine.Debug.LogWarning("Invalid callback");
return default;
}
EventInstance instance = new EventInstance(_event);
if (!instance.isValid()) {
UnityEngine.Debug.LogWarning("Invalid event instance");
return default;
}
HandlePolyphony(instance, type);
return default;
}
static void HandlePolyphony(EventInstance instance, EVENT_CALLBACK_TYPE type) {
instance.getDescription(out EventDescription description);
if (m_Polyphony.ContainsKey(description)) {
PolyphonyInfo info = m_Polyphony[description];
if (type == EVENT_CALLBACK_TYPE.SOUND_PLAYED) {
if (info.CurrentPolyphony >= info.MaxPolyphony) {
instance.clearHandle();
instance.setCallback(null);
instance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
instance.release();
}
else info.CurrentPolyphony = Mathf.Min(info.CurrentPolyphony + 1, info.MaxPolyphony);
}
else if (type == EVENT_CALLBACK_TYPE.SOUND_STOPPED) {
info.CurrentPolyphony = Mathf.Max(info.CurrentPolyphony - 1, 0);
}
}
}
}