Simple sound physics script

hey everyone, ive come here with a script ive been working on for the past few weeks

its a customizable script that uses Raycasts and EqualizerSoundEffects to dampen ingame sounds based on factors like whether it is obstructed by something, how far it is away from the camera, and what material the obstruction is

i hope you all enjoy it:


testing place


script settings

  • refresh_rate - how often the raycasts and equalizer values for each sound are handled
    (set to “runservice” to run every frame; set to a positive number to run that number of times per second)

  • ignore_players - whether players should obstruct sound or not

  • ignore_player_sounds - whether to consider player sounds or not

  • ignore_materials - whether the materials of parts should affect sound obstruction or not

  • minimum_size - the minimum size for a part to be to obstruct sound, in studs
    (applies to each x/y/z value)

  • maximum_transparency - the maximum transparency part could be to obstruct sound

  • reverse_raycast - whether the material should be determined by either the furthest or closest part from the sound to the camera

  • manual_sound_list - exclusive list of sounds for the script to use
    (set to a table of sounds for the script to only use the sounds in the table, set to false to autodetect sounds instead)

  • initial_sound_list - list of sounds that will always be used
    (ignored if manual_sound_list is enabled)

  • initial_ignore_list - list of parts that the raycasts should always ignore

  • default_values - the equalizer values given to sounds that dont already have an equalizer instance

  • distance_falloff - scale for sound falloff over distance
    (positive numbers recommended)

  • obstruction_falloff - scale for sound falloff over distance, when obstructed by a part
    (positive numbers recommended)

  • obstruction_values - how much sound should be obstructed by a part, in decibels
    (positive numbers recommended)

  • material_obstruction_values - how much the material of a part affects the obstruction of sound, in decibels
    (ignored if ignore_materials is enabled)

  • lerp_alpha - the speed at which eq values will interpolate
    (number between 0 and 1; the values will not change if set to 0)


important things to remember

  • script needs to be placed in StarterPlayerScripts to function as intended

  • starts to lag at about 5000 rays per second

  • ignore_player_sounds set to false will likely have a large performance impact if there are many players ingame

  • because of a raycast limitation, underwater sounds can only be dampened if the camera is outside of the terrain; you will need to use Region3 or some other method if you want to dampen underwater sounds while the camera is also underwater


model link

https://www.roblox.com/library/6951309163/simple-sound-physics-script

source and changelog

90 Likes

This is gonna enable developers of all kinds to easily add ambience into their games, thank you for this invaluable resource! :slight_smile:

3 Likes

This reminds me from the hack week 2020. Great resource!

3 Likes

I made something similar to this a couple of weeks ago. Although, mine does not take the material into account when obstructed. Either way, this is quite cool!

3 Likes

Exactly what I was thinking! I’ve always wondered how they could make that.

1 Like

Awesome! Can’t wait to play around with this!

1 Like

Interesting! May I ask how you get the directions for the rays? I’ve seen NieR automata make a post on this, where it describes them sending out rays in random directions and then sample their results. Do you also do the same? And if so, are the results biased towards the poles? Or do you use something like a fibonacci sphere to get a random direction, with equal chance to go in any direction?

P.S, you really should put this on github, in case people would want to fork and/or contribute! :grinning_face_with_smiling_eyes:

EDIT: Just opened the localscripts source, and my god what am I even looking at…

How is the reverb calculated? As in, what formula is used?
If possible, I’d like to input a formula I use into the script, which is (2*distance)^-2 * cos(-rayDir * surfaceNormal) * (1 - materialAbsorptionCoefficient), but I don’t know where to even begin editing this…

2 Likes

sorry if the code is a bit messy… im a novice scripter
you did mention uploading it to github so people can contribute though, so i will try doing that

for the rays, i just send a ray directly from the camera to the sound part by using this code:
direction_vector = (camera_position - sound_part.Position).Unit * (camera_position - sound_part.Position).Magnitude
or this code if in reverse (sound part to camera):
direction_vector = (sound_part.Position - camera_position).Unit * (sound_part.Position - camera_position).Magnitude

about reverb, i have thought about coding it in, because even though a sound can be obstructed by something directly, sound can still come in from around the obstruction

however, itll probably need many extra rays to be sent per sound, and the math/logic behind those extra rays is definiely beyond my experience

If this is of any help, here is the code I use for my game:

local Players = game:GetService('Players')
local RunService = game:GetService('RunService')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local GameSettings = UserSettings().GameSettings

local LocalPlayer = Players.LocalPlayer
local Character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()

local LocalTorso = Character:WaitForChild('Torso')
local OceanModule = require(ReplicatedStorage.Modules.Ocean)

local Params = RaycastParams.new()
Params.FilterDescendantsInstances = {Character, workspace.Rays, OceanModule.OceanFolder}
Params.FilterType = Enum.RaycastFilterType.Blacklist
local function Raycast(Point, Direction)
	local Result = workspace:Raycast(Point, Direction, Params)

	if Result and Result.Instance then
		return Result
	end
end


local function FibbonaciSphere(N, Len)
	local RandomNum = math.random() * N

	local Points = {}
	local Offset = 2 / N
	local Increment = math.pi * (3 - 5^0.5)

	for Point = 1, N do
		local Y = ((Point * Offset) - 1) + (Offset / 2)
		local R = (1 - Y^2) ^ 0.5

		local Phi = ((Point + RandomNum) % N) * Increment

		local X = math.cos(Phi) * R
		local Z = math.sin(Phi) * R

		table.insert(Points, Vector3.new(X * Len, Y * Len, Z * Len))
	end
	return Points
end
local FibbDirections = FibbonaciSphere(1000, 100)


-- 0 = perfect reflection
-- 1 = absorbs all
local MaterialAbsorptionRates = {
	Plastic = 0.2,
	Grass = 0.7,
	SmoothPlastic = 0.5,
	Cobblestone = 0.65,
	Concrete = 0.7,
	Fabric = 0.7,
	Wood = 0.5,
	WoodPlanks = 0.5,
	Brick = 0.7,
	Sand = 0.8,
	Ice = 0.3,
	Metal = 0.4,
	Marble = 0.2,
	Granite = 0.2,
}

local ReverbEnabled = false
local EditingSounds = {}
local function InitSound(Sound)
	--SoundSystem:Attach(Sound)
	local ReverbFX = Instance.new('ReverbSoundEffect')

	ReverbFX.DecayTime = 1.5
	ReverbFX.Density = 0.4
	ReverbFX.Diffusion = 1
	ReverbFX.DryLevel = -2
	ReverbFX.WetLevel = -10
	ReverbFX.Enabled = ReverbEnabled

	ReverbFX.Parent = Sound
	table.insert(EditingSounds, {
		Sound = Sound,
		Object = Sound.Parent,
		Reverb = ReverbFX
	})
end

local NumDirections = #FibbDirections
local function RandomDirection()
	return FibbDirections[math.random(1, NumDirections)]
end
local function CalculateReverb(Distance, RayDir, Normal, Absorption)
	return (2*Distance)^-2 * RayDir:Dot(Normal) * ((1 - Absorption))
end

local function NestedRaycast(OriginCF, Direction, BounceAmount)
	local OriginPos = OriginCF.Position
	local InitHit = Raycast(OriginPos, Direction)

	if not InitHit then
		return 100, 0
	end

	local ReverbSum = 0

	local InitNormal = InitHit.Normal
	Direction = Direction - (2 * Direction:Dot(InitNormal) * InitNormal)

	local AbsorptionMult = MaterialAbsorptionRates[InitHit.Material.Name] or 0.5
	for Bounce = 1, BounceAmount - 1 do
		local Hit = Raycast(OriginPos, Direction)

		if Hit then
			local HitPosition = Hit.Position
			local Normal = Hit.Normal

			AbsorptionMult *= 1 - (MaterialAbsorptionRates[Hit.Material.Name] or 0.5)
			ReverbSum += CalculateReverb((HitPosition - OriginPos).Magnitude, Direction, Normal, AbsorptionMult)
			-- r = d - (2 * d:Dot(n) * n)
			Direction -= 2 * Direction:Dot(Normal) * Normal
		else
			return ReverbSum / Bounce --, (InitHit.Position - OriginPos).Magnitude
		end
	end

	return ReverbSum / (BounceAmount - 1)--, (InitHit.Position - OriginPos).Magnitude
end


local NumRays = 10
local BounceNum = 4

local SoundSpeed = 20

local function InitChild(Obj)
	if Obj:IsA('Sound') then
		InitSound(Obj)
	end
end
local function InitCharacter(PlayerCharacter)
	local Torso = PlayerCharacter:WaitForChild('Torso')
	for _, Obj in next, Torso:GetChildren() do
		InitChild(Obj)
	end
	Torso.ChildAdded:Connect(InitChild)
end
InitCharacter(Character)

local function OnPlayerAdded(Player)
	local Char = Player.Character
	if Char then
		InitCharacter(Player.Character)
	end
	Player.CharacterAdded:Connect(InitCharacter)
end

local function OnChildAdded(Spell)
	wait(0.2)
	for _, Obj in next, Spell:GetDescendants() do
		InitChild(Obj)
	end
end

workspace:WaitForChild('Spells', 15).ChildAdded:Connect(OnChildAdded)
workspace.VFX.ChildAdded:Connect(OnChildAdded)

for _, Player in next, Players:GetPlayers() do
	if Player ~= LocalPlayer then
		OnPlayerAdded(Player)
	end
end
Players.PlayerAdded:Connect(OnPlayerAdded)

local Camera = workspace.CurrentCamera

local function ToggleAllSounds(Bool)
	for Idx, Data in next, EditingSounds do
		local Sound = Data.Sound
		if not Sound:IsDescendantOf(workspace) then
			table.remove(EditingSounds, Idx)
			continue
		else
			Data.Reverb.Enabled = Bool
		end
	end
	ReverbEnabled = Bool
end
RunService.Heartbeat:Connect(function()
	debug.profilebegin('Reverb SFX')
	local ShouldBeDisabled = GameSettings.SavedQualityLevel.Value <= 6

	if ShouldBeDisabled then
		if ReverbEnabled then
			ToggleAllSounds(false)
		end
	elseif not ShouldBeDisabled then
		if not ReverbEnabled then
			ToggleAllSounds(true)
		end

		local CameraCF = Camera.CFrame
		local CameraPos = CameraCF.Position

		local PlayingSounds = {}
		for Idx, Data in next, EditingSounds do
			local Sound = Data.Sound
			if not Sound:IsDescendantOf(workspace) then
				table.remove(EditingSounds, Idx)
				continue
			end

			if Sound.IsPlaying and (CameraPos - Data.Object.Position).Magnitude < Sound.RollOffMaxDistance then
				table.insert(PlayingSounds, Data)
			end
		end

		if #PlayingSounds == 0 then
			return
		end

		local ReverbAvgSum = 0
		local OriginCF = LocalTorso.CFrame

		for _ = 1, NumRays do
			ReverbAvgSum += NestedRaycast(OriginCF, RandomDirection(), BounceNum)
		end

		if ReverbAvgSum > 0 then
			local AverageReverb = ReverbAvgSum / NumRays * BounceNum

			for _, Data in next, PlayingSounds do
				local Reverb = Data.Reverb

				Reverb.WetLevel = math.min(AverageReverb * -0.2, -5)
				Reverb.DecayTime = AverageReverb / SoundSpeed
			end
		end
	end

	debug.profileend()
end)

an issue with this is that it doesn’t properly average all the reverbs - the reverb can get very high if you have two walls close to yourself, which becomes an issue. Hopefully this still helps, though :grinning_face_with_smiling_eyes:

1 Like

could you opensource the place showed in the post? it might be kinda easier for testing the settings

This, this is really cool! I would’ve never thought I’d see something like this for roblox, but here we are. I gotta give it to you man, this is great. If you were able to add reverb to this, it would be super cool, but besides that, nice work!

@PysephDEV i put the script on github and put the link in the post

2 Likes

What would be the appropriate settings to make certain materials soundproof?

I have a sugguestion.

What if you had the system detect how large an area a player is in and how sound reflective the materials are to make the sound more reverb ish

changing each value in a material table to 90 should silence the sound, no matter what the initial eq values of the sound may be: {high = 90, mid = 90, low = 90}
(range of values is -80 to 10, so subtracting 90 or greater should always result in the lowest value)

another tip - you can negate the effect of obstruction for certain materials by putting in the opposite respective values of the default ones
example:
obstruction_values = {high = 12, mid = 6, low = 3}
ForceField = {high = -12, mid = -6, low = -3} - forcefield material parts will have no effect on sound

1 Like

Hey dude, thanks so much for this. I was searching all day for something like this to put into my cafe and I never found it. Lucky me I stumbled upon this post. I will be using this in my cafe game. Again, thanks so much for this. Better bookmark this before I go to bed :upside_down_face:
-Cats767_99

yoooo epic sound script except what music is it in the video?i’ll be sure to use this in my creations

That’s really usefull thank you dev! :slightly_smiling_face:

Thank you so much for this script!

This is really cool and really useful. Although, it would be great to make the music fade out a little bit if you were underwater. Just like this resource I just found: Underwater Muffle
It’s really easy to add in yourself if you want the sound to fade out.

1 Like