How to store sounds and music in your game correctly

Hello everyone! In this post I would like to make tutorial on how to store sounds and music correctly. What prompted me to do it is that new developers basically store sounds and musics in the workspace, and most of them are not used often. It is bad…

(Sorry if in this post is bad grammar)

So, you probably get a question “why shouldn’t I store them in workspace?”

– A long sound can take a lot of client memory usage. If the client memory usage will be very big it can make your game unplayable and unoptimized for some devices. A better way to avoid this is to store them in ServerStorage. When you need to use one of them you just copy in the Workspace and use it. When you don’t need it or it was used already you remove it.

If you would like to see how much memory does sounds take in total you can look in dev. console > Memory (See screenshot).

Next step you need to scroll down till you find “Place memory” and in it find “Sounds” (See video).

If you saw that in your game has more than 50 MB of sounds usage then this post is for you. Please make sure you know the basics of scripting to use this tutorial.

How removing long sounds/musics from the Workspace will help to lower client memory usage?
– Watch the video:

So, below you will see steps how to store sounds and musics proper, and manage them:

Step 1. Transfering sounds.
If your sounds and musics are store to play as a background sound or music then you need to create any folder in ServerStorage with the name “SoundStorage” and transfer them to this folder. If you have a background sound that is always looping (like ambience effect, etc) then you don’t need to transfer it.
image
You can also sort your sounds like on the screenshot (optional).

ServerStorage - a good storage where you can store tools, sounds, etc. Local scripts can’t get access to the storage because it is only server-sided storage, so any attempt will lead to an error.

Step 2. Creating a folder and module
Create a folder in the Workspace and name it “LoadedSounds”.

Create a module in the ServerScriptService and name it “SoundManager”.

Step 3. Setting up the module
3) Make the module to be empty and put this code in the module that was created by me:

local ServerStorage = game:GetService("ServerStorage")
local Debris = game:GetService("Debris")

local SoundStorage = ServerStorage.SoundStorage
local SoundStorageDescendants = SoundStorage:GetDescendants()
local LoadedSounds = workspace.LoadedSounds

local module = {}

local CurrentUsedSounds = {}

local function LoadSound(SoundName,Parent)
	Parent = Parent or LoadedSounds

	for _,Child in pairs(SoundStorageDescendants) do
		if Child.Name == SoundName then
			if not Parent:FindFirstChild(SoundName) then
				local S = Child:Clone()
				S.Parent = Parent
				table.insert(CurrentUsedSounds,S)
				return S
			else
				warn("The sound '" .. SoundName .. "' is already in use.")
				return
			end
		end
	end
	warn("No sound with the name '" .. SoundName .. "' has been founded in " .. Parent:GetFullName() .. ".")
end

function module:FindAudioInLoaded(SoundName,NeedPlay) -- Finds a sound in "LoadedStorage" and returns the info about sound.
	if NeedPlay == true then
		local S = LoadedSounds:FindFirstChild(SoundName)
		if S then
			S:Play()
		end
		return S
	end
	return LoadedSounds:FindFirstChild(SoundName)
end

function module:PreloadAudio(SoundName,Parent) -- Preloads a sound
	local Sound = LoadSound(SoundName,Parent)
	if Sound==nil then return end

	return Sound
end

function module:PlayAudio(SoundName,Parent) -- Play a sound
	local Sound = LoadSound(SoundName,Parent)
	if Sound==nil then return end

	Sound:Play()

	return Sound
end


function module:DestroyAudio(Sound,TimeOfLife) -- Destroys a sound
	TimeOfLife = TimeOfLife or 0

	if typeof(Sound) == "Instance" then
		table.remove(CurrentUsedSounds,CurrentUsedSounds[Sound])
		Debris:AddItem(Sound,TimeOfLife)
	else
		local RealSound = module:FindSoundInLoaded(Sound)
		if RealSound then
			table.remove(CurrentUsedSounds,CurrentUsedSounds[RealSound])
			Debris:AddItem(RealSound,TimeOfLife)
		else
			warn("No sound with the name '" .. Sound .. "' has been founded in " .. LoadedSounds:GetFullName() .. ".")
		end
	end
end

function module:RemoveAllAudiosInLoaded() -- If your game is regenerating in some moment then this function is for your game. 
	for _,v in pairs(LoadedSounds:GetChildren()) do
		v:Destroy()
	end
	for _,v in pairs(CurrentUsedSounds) do
		if v~=nil then
			v:Destroy()
		end
	end
	table.clear(CurrentUsedSounds)
end

return module

Step 4. How to use it
After setting up the module here is 5 functions in the module that you can use for sounds managing:

1 - FIndAudioInLoaded
2 - PreloadAudio
3 - PlayAudio
4 - DestroyAudio
5 - RemoveAllAudiosInLoaded

“FindAudioInLoaded” function is find out a sound in “LoadedSounds” folder in Workspace by the name and returns the data about the sound. Here is an example how to use this:

local Module = require(game.ServerScriptService.SoundManager)
local Sound = Module:FindAudioInLoaded("AnySound")

task.wait(3)

Sound:Play()

If you need to play it instantly and you don’t need to set it you can add in the “FindAudioInLoaded” function the second argument “true”:

local Module = require(game.ServerScriptService.SoundManager)
Module:FindAudioInLoaded("AnySound",true)

“PreloadAudio” function is find out a sound in “LoadedSounds” folder in Workspace and copy the sound to the folder. If you need to play it instantly you can use “PlayAudio” function. Both of functions are returning the data of the sound. Here is examples how to use them:

local Module = require(game.ServerScriptService.SoundManager)
local Sound = Module:PreloadAudio("AnySound")

task.wait(3)

Sound:Play()
local Module = require(game.ServerScriptService.SoundManager)
Module:PlayAudio("AnySound")

By default the parent of the sound sets to “LoadedSounds” folder in Workspace. If you need to play/preload it in a certain part just add in the function the second argument a parent in which you need to play it:

local Module = require(game.ServerScriptService.SoundManager)
local Sound = Module:PreloadAudio("AnySound",workspace.Part)

task.wait(3)

Sound:Play()
local Module = require(game.ServerScriptService.SoundManager)
Module:PlayAudio("AnySound",workspace.Part)

“DestroyAudio” function is find out a sound in “LoadedSounds” folder and destroy it. Here is an example how to use it:

local Module = require(game.ServerScriptService.SoundManager)
local Sound = Module:PreloadAudio("AnySound")

task.wait(3)

Sound:Play()

task.wait(3)

Module:DestroyAudio(Sound)

if you need to delay audio remove without needing to add “task.wait(3)” you can add the life time before it removes in the second argument of “DestroyAudio” function:

local Module = require(game.ServerScriptService.SoundManager)
local Sound = Module:PreloadAudio("AnySound")

task.wait(3)

Sound:Play()

Module:DestroyAudio(Sound,3)

You can also remove the sound by its name but make sure the sound is located in the “LoadedSounds” folder.

“RemoveAllAudiosInLoaded” function is remove every sound that was created but wasn’t removed by the module. This function can be used in the map regeneration, etc.

Now you know how to use the module. Just remind: don’t left any sound in the “LoadedSounds” folder in Workspace or in any part if it won’t be used during a period. After playing just remove it and when you need to use the sound again you can just add it back. Whatever any sound will adds to Workspace the sound memory usage will raising. But when they will remove from the Workspace the sound memory usage will droping also.

Module features:
– This module won’t let copy a sound in that place where it is already exists unless it will be removed.

Recommendations:
– It is recommend to store sounds in the Workspace that are less than 10-15s of the length because their weight are so small? Well, this is on your decision. I recommend to not store sounds in the workspace with the length of more than 1 minute.

– if you have a background sounds in which looping is turned on you need to store them in the Workspace because copying from the ServerStorage to Workspace does not make sense.

I think, this tutorial will help games that have a lot of sounds in the workspace. If you have any question or I did anything wrong, feel free to say it below.

16 Likes

what if we need it for the client, like for GUI sounds

Sorry, but this tutorial is only for server-sided sounds. And btw, GUI sounds does not take too much memory. Their length ~ 1-10s (Depends on what sounds will be used).

And if we put sounds in SoundService, they will still be stored in client side?

Why you need to transfer them to SoundService? And yes it will be available on client side but on the sound memory usage it won’t affect. A better way is to store them in ServerStorage. But they wont be available on client side which makes sound memory usage to be low.

By far the most efficient and scalable solution is to serialise sound data (value instances or a folder with attributes are suitable) and create sounds during runtime when required.

If they’re played consistently in the same location in the DataModel then it acts as a cache of sorts. Clearing the sounds out of this location will remove them from the cache and achieve the same effect of this post.

This method beats the one mentioned in this post by far:

  1. You can make a ModuleScript to handle sound playback and use it on both client and server. No need for any special behaviour.
  2. Improves overall memory usage. If player 1 needs a sound then it’ll load for them and them alone, player 2/3/4 etc. won’t load it unless they need it themselves.
  3. With an effective cache system it gives you good control over when to clear said cache. This could even be done automatically by watching the Sounds memory usage value.

Constant reparenting or cloning of Instances is more taxing due to replication, and in some cases some players in the game won’t even need the sound that’s being put in Workspace.

With all this in mind though, most games will never be affected negatively by the memory usage (or even network usage from cloning/reparenting ServerStorage sounds) unless you’re using hundreds of sounds and/or if sounds are rapidly played. Only optimise if you need to!

8 Likes

I use the above method (specified by xonae) for my experience which is very heavy on sounds and I was able to reduce my memory usage by up to a full gigabyte. This also helped me recapture real estate on join times (besides setting up a system to hand replicate content).

How to store your sounds is to only store their data and pull them in as they’re needed, not change their physical location. If you still have an active Sound instance in the DataModel, some peer is going to bear the cost of caching the sound’s data. You don’t want this.

2 Likes

When you create the sound and set its SoundId to it will load the sound in 1-2s which make delay for playing the sound.

Btw, yeah, using remote event to load sound when player is near to this sounds good. Most of the job shouldn’t do server side that is only for players. But i don’t see to do this for sounds.

Well, i know that some of the devices support a specific amount of client memory usage. Exceeding max client memory usage will lead to your game crash. And btw, for my game, which has 100k parts and more than 1000 sounds this will help with the optimization.

There is an initial delay when playing the sound for the first time, yes, but this is because the sound is not loaded at all. All of its properties are only stored as data.

The caching effect means that this only occurs once and never again, which is acceptable given you could store literally up to thousands of sounds in this manner with close to no impact until the sounds are played, and even then they’ll get unloaded the moment you clear that cache.

We use this approach in Arsenal and it’s never been an issue for players. If the delay is really that bad then you can effectively preload the sounds prior to their use, so there’s really no downsides to this method.

The method outlined in your post causes unnecessary complication of the process and pawns off the memory usage onto the server, which will still cause issues with big games. In the event that only a single player needed to experience a sound in the world, your approach would load it for all players in the world.

Multiply this by the number of active players in the world for a worst case scenario and you could be looking at a ton of sounds being loaded by all clients when they don’t need it, and that’s not even mentioning the bandwidth being used to replicate these instances to the clients too.

Please don’t advertise a method of “correctly storing sounds and music” when it is, in most cases, less performant and less user friendly than the approach people normally take, or other approaches which actually perform better.

This isn’t meant to discourage you from trying to help other people by making tutorials like this, just make sure what you’re posting is correct and do a lot of research!

7 Likes

Would it be okay if I could just put this into SoundService instead? If yes, can I still play sound on the server or the player’s client?

Also, if this tutorial is only for server-sided sounds, then why is the title name a bit misleading?

Server-side sounds will be replicated for all players. As i said above there wont be any effect If you put it in the SoundService.

1 Like