Procedural sound occlusion and reverb

Hello! I’ll keep this brief: I’m wondering what the best method of implementing procedural sound effects such as occlusion (muffling the sound when there’s an obstruction between you and the source) and reverberation (echoes in large closed areas, such as caves).

I have an idea of how to achieve this: Using raycasting to detect obstructions and whether or not the player is in a closed off area or a valley or whatever. For every major sound source that’s within range, cast a ray to it. If something blocks the ray, determine if it’s thick enough to muffle the sound, and apply effects as appropriate. So far so good.

Reverberation, however, is more difficult - I’d have to cast multiple long rays to check whether or not a player is just in a very large enclosed space (such as large caves or valleys) - or just in the open. Long raycasts, especially multiple a second, are pretty costly.

So my question here is if I’m on the right track. Are there any examples of performant procedural sound effects I can learn from?

2 Likes

I think in the Cool Creations section’s What Are You Working on pinned thread, that *Tyridge had made a voxel based sound script which flood fills voxels from a sound source after the map changes, had very convincing muffling and you could simply do it the opposite way around, going from the player and flood filling until you cannot reach any other voxels, and the number of voxels you found would be a measure of how large the space was.

Edit: Tyridge not ScriptOn, thank you Keys

2 Likes

https://devforum.roblox.com/t/what-are-you-working-on-currently-2019/218270/1781

2 Likes

This only worked well because my game had a voxel based map with uniform cells. Sounds like what OP requires is a bit different.

@Auhrii unless your map changes, you may be better off not trying to calculate anything during runtime and just tagging certain regions to be what you want. In my Wild West project for instance, we have the different biomes drawn out with parts and have set lighting properties for each biome. We then convert the biome map to a 2D grid of color information, make it so the cell colors interpolate naturally into the other biomes/cells, and get what “pixel” your player is in. May seem irrelevant but the point is you don’t always need to calculate everything, just cache enough information to determine what you need done

Seems like in your case if you want a cave to sound cave-ey or an open area to not have reverberation you could do something similar or just simply tag bounding regions/shapes as such.

Is there any reason you want this to be procedural?

6 Likes

I’m still on the fence about having major map-changing events, as there’ll be orbital strikes and stuff like that. Things that could destroy bridges, shut off caves and the like, but I could still use a pre-baked system. Just need to throw some conditions in there.

The wiki page for SoundService.AmbientReverb has a code example of this!

It’s a prebaked system, but useful nonetheless.

Wiki

Dynamic Reverb System

The code in this sample, when ran from a LocalScript , will change the SoundService.AmbientReverb property of SoundService when the player is inside a BasePart tagged using CollectionService .

To add or remove tags and reverb types, change the entries in the ‘reverbTags’ table.

local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local SoundService = game:GetService("SoundService")
 
local localPlayer = Players.LocalPlayer
 
-- define tags
local reverbTags = {
	["reverb_Cave"] = Enum.ReverbType.Cave
}
 
-- collect parts and group them by tag
local parts = {}
for reverbTag, reverbType in pairs(reverbTags) do
	for _, part in pairs(CollectionService:GetTagged(reverbTag)) do
		parts[part] = reverbType
	end
end
 
-- function to check if a position is within a part's extents
local function positionInPart(part, position)
	local extents = part.Size / 2
	local offset = part.CFrame:pointToObjectSpace(position)
	return offset.x < extents.x
		and offset.y < extents.y
		and offset.z < extents.z
end
 
local reverbType = SoundService.AmbientReverb
 
while true do 
	wait()
	if not localPlayer then
		return
	end
 
	local character = localPlayer.Character
 
	-- default to no reverb
	local newReverbType = Enum.ReverbType.NoReverb
 
	if character and character.PrimaryPart then
		local position = character.PrimaryPart.Position
 
		-- go through all the indexed parts
		for part, type in pairs(parts) do
			-- see if the character is within them
			if positionInPart(part, position) then
				-- if so, pick that reverb type
				newReverbType = type
				break
			end
		end
	end
 
	-- set the reverb type if it has changed
	if newReverbType ~= reverbType then
		SoundService.AmbientReverb = newReverbType
		reverbType = newReverbType
	end
end
10 Likes