Looping silence and best practices

I was doing this for a level of convenience, but I’m starting to realize that there are cases where this might not perform or behave ideally and I wanted to double check. Here’s my use case:

I have a simple elevator that moves up about the character’s height to the next level, and stays there until you use it again. Nothing about the art or anything needs an idle sound, but I did decide that it should have a different sound for up as it does for down.

Quick relevant tangent: Elsewhere we have objects that can be rolled, which roll as far as they can before hitting a wall. Before implementing FMOD we had a separate sound for rolling and for hitting something, but once FMOD was in I realized I could move the hit sound to the same event and just play it when it exits the loop when stop is called.

So for the elevator, I figured it would work to have the same format, except the loop isn’t for moving, it’s for sitting at the second level. So I figured it would be efficient to have just one FMOD event that plays the up sound when it starts moving, which ends when it gets to a loop region with nothing in it. Then for going back down it just exits that loop immediately and plays the downward sound. Exact same logic as something that rolls.

But I started thinking about certain situations where that implementation might break and I’m wondering if that approach is considered a bad practice for those reasons. They are:

  • If the game is saved while the elevator is in the up position, when it is loaded again, will the event playback still be in the loop region and not have to start again? (preferably silently, but I don’t have a reason to expect that)
  • Is there any significant performance cost to having something play silently far away that’s not needed anymore? I feel like there might not be since FMOD already handles the voice count in a really intelligent way, and it’s playing actual silence, so I doubt that system would have any issue deciding it’s not important.
  • If that part of the level is unloaded by our culling system, does FMOD provide an easy way to save the entire state of that event instance to be recalled back later when it is turned back on? But that’s the thing, it’s not actually “turned back on”. It’s been removed entirely, so actually it’s essentially doing everything that it does when instantiated from scratch on an empty game object. All the components are added fresh in their default state, and all its previous properties are then set to what they were before. Or at least that’s what we’re programming the system to do. Does the FMOD API include anything that can handle that saving and reloading of all properties for us or at least some of it?

Are these the best practice related reasons that I should just use two events, one for up and one for down? The only reason I’m asking questions (that could easily not even need to be asked in the first place) about something so simple if I just did with two events is because I’m kind of trying to learn the ways that are best to do by default every time. And one thing that I do know is usually a best practice is:

“When deciding between more than one way to achieve the same result and all else is equal, pick the option that uses fewer events.”

At least that’s what I’ve been told. But I’m realizing now that I only ever just accepted it since it makes intuitive sense. So maybe the real question here is: is opting for a lower event count when everything else doesn’t matter actually a best practice? And why or why not?

If event count doesn’t matter unless it involves more source assets (it doesn’t in my case), then I’m going to forget about those questions above and just use two events, but yeah, this is really about knowing the best way to do this for every time I do it in the future. After all, it can cut the event count in half for everything that needs this implementation.

Sorry for such a long post, but thank you.

It depends on how you serialize the game when saving and loading. With your current setup, you would need to serialize the event instance’s timeline position so it would resume from exactly where it was.

FMOD has a virtualization system, which causes events/sounds that become inaudible to become “virtual” and no longer be processed, while still tracking the playback position of the sound. Events/sounds that become audible again stop being “virtual” and become “real”. This saves a lot on resource use. However, this isn’t the same as a culling system; not processing audio reduces resource usage, but there is still some overhead from continuing to process the event/sound.

You can read more about the virtual voices system in the Core API Managing Resources chapter of the FMOD Engine docs.

The FMOD API doesn’t have any inbuilt functionality for this, though you can retrieve info like 3D attributes, parameter values, and timeline position from the API. If you completely stop and release an event when culling, it may not be possible to reinstantiate it in the exact same state, especially if you’re using aynchronous instruments or event instruments.

The specifics of “serializing” your events depend entirely on how both your culling system works, and how your FMOD events and project are set up. I’d generally recommend designing your events in such a way as to decouple their playback state from culling/serialization as much as possible to avoid worrying about this issue, barring any particularly important edge cases.

I would broadly agree with this sentiment, but obviously it very much depends on what you need to do with the event, your Studio project, and how your game operates. It’s also a very minor case, since asset playback and processing from effects usually incurs more resource usage than have another event instance anyway.

That said, I’d recommend against premature optimization in all but the most obvious cases (i.e. not playing instances of resource intensive effects like convolution reverb on events), since it’s generally a lot easier to implement the behavior you need, identify any issues with the Studio Profiler, and then optimize from there.


As for the actual best way to implement the elevator audio you’ve described, I’d suggest doing something like the following:

  1. Create an event with a labeled parameter that controls whether the elevator is moving up, moving down, or is stopped. Make sure stopped is the default value.
  2. On the parameter’s parameter sheet, place the up/down audio on their respective parameter values
  3. Ensure the up/down instruments have “Cut” enabled, and modulate their volumes with a AHDSR modulator to have them blend in/out when played

Then in your game, whenever the elevator needs to move, you play the event instance, making sure to set the parameter to match the elevator’s state: moving up, moving down, or stopped.

With your current setup, you would need to serialize the event instance’s timeline position so it would resume from exactly where it was.

Yeah, that’s one thing I remember looking into quite some time ago. Way back then we were evaluating the viability of building a rather ambitious save system (a frame perfect save similar to a ‘save state’ on an old SNES emulator). Needless to say, we have since abandoned that idea, but timeline position was the big thing that made us think it would be realistic to do with audio. However I’m curious about something else you said.


You were referring to culling at the time, but regardless of culling vs saving, you had said:

it may not be possible to reinstantiate it in the exact same state, especially if you’re using aynchronous instruments or event instruments.

I hadn’t considered the implications of async instruments and nested/referenced events when thinking about this. Is there no way to get the playback position of an async instrument, or I suppose maybe the implication is anything on a timeline? Which also begs the question: can you get the playback position of something played by a parameter or action that does not have a timeline (like your alternative method of implementing the elevator?) Specifically, what about a nested or referenced event that does have a timeline? Is that not available simply because it was called within another event’s timeline?


not processing audio reduces resource usage, but there is still some overhead from continuing to process the event/sound.

First of all, thank you for the link to the documentation. That has a lot of relevant information and I will be referring back to it many times I’m sure. There are a couple things I don’t think it goes into though. Mostly, I’m still a little unclear about the granularity of the concept of a Channel. Is a channel equivalent to a single event’s output? The final audio signal sent from the individual event after its track outputs have been summed together and any effects have been applied?
ie: Event master track output = Channel?

And it’s channels that the doc is talking about being virtual vs real, so that would mean it’s specifically an event instance then, right? I ask because I feel like this must have implications on the importance of event count - If each event instance is a channel, the level of processing that does or does not get virtualized, how much does having fewer events help if that means that they have more tracks, effects, or parameters?


On the subject of culling, you mentioned:

I’d generally recommend designing your events in such a way as to decouple their playback state from culling/serialization as much as possible to avoid worrying about this issue

That sounds like a good approach, but I don’t think I understand what you mean by “decoupling their playback state”. Are you referring to things like not using too many parameters from different game objects, the references to which will need to be reestablished after being unculled? If you meant something else by that, I would love it if you could explain that more, because that sounds like a really useful approach, but I might not understand what you’re talking about.


Ok, finally the thing I actually meant to be asking about originally (lol sorry):

As for the actual best way to implement the elevator audio you’ve described, I’d suggest doing something like the following:

  1. Create an event with a labeled parameter that controls whether the elevator is moving up, moving down, or is stopped. Make sure stopped is the default value.
  2. On the parameter’s parameter sheet, place the up/down audio on their respective parameter values
  3. Ensure the up/down instruments have “Cut” enabled, and modulate their volumes with a AHDSR modulator to have them blend in/out when played

Then in your game, whenever the elevator needs to move, you play the event instance, making sure to set the parameter to match the elevator’s state: moving up, moving down, or stopped.

That’s a cool idea. I never thought of doing a moving object essentially the same way as footsteps. A couple questions about that:

  • If there was a stop sound, with this method that would just go in the Stopped parameter value right? And would not use cut I believe, it would just get triggered when the parameter switched it over and otherwise stopped the event?
  • If they needed different stop sounds, I would think that’s the point where it’s just better to have different events, or maybe just one event with a nested or referenced event for each set up the way you described? Otherwise you might need to have an UpStop and DownStop value for the parameter and that’s starting to defeat the purpose I would think.
  • Will something even play when the event receives a parameter change that should start an instrument, but also calls Stop at the same time? Actually I think it would, since Stop doesn’t actually stop playback, but it more so just starts the event’s stop behavior, right?

Anyway, thank you so much for all the information in your last response, Leah. And sorry for being so verbose and asking so many questions. For what it’s worth I do really appreciate all the info and it is super helpful.

There isn’t any way to get the playback position of an async instrument, or any instrument played by a parameter or action sheet, because the playback position of the instrument is not exposed by the Studio API.

Channels are a low-level Core API concept. A Channel is a playing instance of any sound. A ChannelGroup allows attributes to be set on a group of Channels collectively. In Studio, a Channel is any playing asset in an instrument, which is routed into a series of hierarchical ChannelGroups (Event Audio Tracks → Event Master Track → Buses → Master Bus), which combines the signal from each channel and processes the signals with effects along the way to form the final mix.

The Core API and Studio API virtualize on different levels. The Core API will virtualize channels when they become inaudible, or exceed the limit set by System::setSoftwareChannels. Separate from this, Studio lets you set a limit on the number of event instances that exist at once, with any event instances exceeding that limit being virtualized in their entirety, i.e. all channels/channel groups. As a result, it’s possible for channels in an event instance to be virtual without the event instance itself being virtual.

As for whether or not you should have more or less events, and how virtualization will impact performance based on this, it really isn’t something to worry about until you’re actually hitting any voice count limits or resource issues, which is rare unless you’re playing an extremely large amount of event instances at once without any culling.

This is correct - it means you won’t have to worry about serializing and re-establishing the exact state of unculled events.

This would depend on what exactly you wanted for the stop sound, but yes.

It depends on what works best for your workflow. You could have:

  • Additional parameter states
  • A separate parameter for the stop type, and use a nested event on the Stop parameter value which plays the stop sound based on the stop type parameter
  • Separate events

Again, it depends on exactly what you need from the event. The Stop sound can be independent from actually stopping the event instance, or coupled to it. If you’re calling EventInstance::Stop in code, you can allow fadeout, which will play an release behavior you set up in Studio.