Sound Optimizations and Debug Tools (#88517)

## About The Pull Request
I was digging through sound code trying to add a few misc `.ogg`'s when
I noticed a horrifying revelation. Sound calculations are broken as
fuck. At first I didn't think it was a big deal until I remembered this
comment in another PR:


![chrome_6rspeYVig1](https://github.com/user-attachments/assets/04c3fa79-c267-43ae-a77f-85b0e8d3108f)

I asked for the profile logs:

```
                                                           Profile results (total time)
Proc Name                                                                                          Self CPU    Total CPU    Real Time     Overtime        Calls
----------------------------------------------------------------------------------------------    ---------    ---------    ---------    ---------    ---------
/proc/playsound                                                                                       4.771       26.841       26.902        0.347       153938
/mob/proc/playsound_local                                                                             9.511       15.689       15.891        0.724       429391

/mob/living/say                                                                                       0.125       19.064       19.945        0.000         2550
/mob/living/carbon/updatehealth                                                                       0.878        8.305        8.341        0.013        49847
/mob/living/carbon/proc/handle_organs                                                                 1.486        9.263        9.300        0.000        70977
/obj/item/organ/internal/on_life                                                                      0.415        0.455        0.561        0.000       340312
/mob/living/carbon/Life                                                                               0.758       30.085       30.123        0.000        71933
/mob/living/proc/Life                                                                                 5.509       39.404       39.512        0.000       401951
```


Yeah, that is _really_ bad. Especially since it is a hot proc that is
used half a million times. Optimizing this would help a decent amount.
So now you are probably wondering, what exactly is the problem?! Let's
take a look.

https://www.desmos.com/calculator/sqdfl8ipgf

Sounds are being broadcast on a large radius which is typically about
~17 tiles. Since the volume of a sound is reduced by distance, the
further away the sound is, the quieter it becomes. You would think, hey
that's fine! Sounds become inaudible at the 17 tile distance limit
right? Except no, they generally become inaudible about 2/3rds of the
way there. (~10 tile range) Sound volume is between 1-100. When the
sound volume is lower than 3, it becomes (pretty much) inaudible.

This means most of the sounds that are being spammed in game everywhere,
you can't even hear!!!

To fix this we used two major optimizations:

1. Add a `SOUND_AUDIBLE_VOLUME_MIN` to ignore any sound lower than this
volume and calculate an audible distance
2. Use the new audible distance to drastically shorten the tile range of
mob listeners using `get_hearers_in_view()`

To give you an idea of how much better this is:

Sounds that had a range of 17x17 (289 turfs) are now 12x12 (144 turfs).
There is also large range machinery like the SM that has a 40x40 (1600
turfs) that is now 30x30 (900 turfs).

It's quite an improvement.

I also went and added a debugging tool to the admin/debug equipment set
that can be found in the debug box. It is a pair of earmuffs that when
worn has any sounds sent to the mob via a `to_chat` message displaying
the sounds max range, distance from player, volume, and sound name. It
is highly recommend to walk while wearing the earmuffs since walking
stops your mob from emitting footstep sounds.

## Why It's Good For The Game
The speed of sound is now faster than ever. Please don't break the sound
barrier.

## Changelog
🆑
refactor: Sound has been heavily optimized and will now ignore low
volume sounds from far away.
admin: Add debugging sound earmuffs to admin equipment inside the debug
box. Wear them to determine a sounds max range, distance, volume, and
sound name. Highly recommended to walk otherwise you will get spammed
with footstep sounds.
/🆑
This commit is contained in:
Tim
2024-12-27 18:42:51 -08:00
committed by GitHub
parent 619888603d
commit 78ff108529
6 changed files with 64 additions and 9 deletions
+38
View File
@@ -17,6 +17,44 @@
#define MAX_INSTRUMENT_CHANNELS (128 * 6)
/// This is the lowest volume that can be used by playsound otherwise it gets ignored
/// Most sounds around 10 volume can barely be heard. Almost all sounds at 5 volume or below are inaudible
/// This is to prevent sound being spammed at really low volumes due to distance calculations
/// Recommend setting this to anywhere from 10-3 (or 0 to disable any sound minimum volume restrictions)
/// Ex. For a 70 volume sound, 17 tile range, 3 exponent, 2 falloff_distance:
/// Setting SOUND_AUDIBLE_VOLUME_MIN to 0 for the above will result in 17x17 radius (289 turfs)
/// Setting SOUND_AUDIBLE_VOLUME_MIN to 5 for the above will result in 14x14 radius (196 turfs)
/// Setting SOUND_AUDIBLE_VOLUME_MIN to 10 for the above will result in 11x11 radius (121 turfs)
#define SOUND_AUDIBLE_VOLUME_MIN 3
/* Calculates the max distance of a sound based on audible volume
*
* Note - you should NEVER pass in a volume that is lower than SOUND_AUDIBLE_VOLUME_MIN otherwise distance will be insanely large (like +250,000)
*
* Arguments:
* * volume: The initial volume of the sound being played
* * max_distance: The range of the sound in tiles (technically not real max distance since the furthest areas gets pruned due to SOUND_AUDIBLE_VOLUME_MIN)
* * falloff_distance: Distance at which falloff begins. Sound is at peak volume (in regards to falloff) aslong as it is in this range.
* * falloff_exponent: Rate of falloff for the audio. Higher means quicker drop to low volume. Should generally be over 1 to indicate a quick dive to 0 rather than a slow dive.
* Returns: The max distance of a sound based on audible volume range
*/
#define CALCULATE_MAX_SOUND_AUDIBLE_DISTANCE(volume, max_distance, falloff_distance, falloff_exponent)\
floor(((((-(max(max_distance - falloff_distance, 0) ** (1 / falloff_exponent)) / volume) * (SOUND_AUDIBLE_VOLUME_MIN - volume)) ** falloff_exponent) + falloff_distance))
/* Calculates the volume of a sound based on distance
*
* https://www.desmos.com/calculator/sqdfl8ipgf
*
* Arguments:
* * volume: The initial volume of the sound being played
* * distance: How far away the sound is in tiles from the source
* * falloff_distance: Distance at which falloff begins. Sound is at peak volume (in regards to falloff) aslong as it is in this range.
* * falloff_exponent: Rate of falloff for the audio. Higher means quicker drop to low volume. Should generally be over 1 to indicate a quick dive to 0 rather than a slow dive.
* Returns: The max distance of a sound based on audible volume range
*/
#define CALCULATE_SOUND_VOLUME(volume, distance, max_distance, falloff_distance, falloff_exponent)\
((max(distance - falloff_distance, 0) ** (1 / falloff_exponent)) / ((max(max_distance, distance) - falloff_distance) ** (1 / falloff_exponent)) * volume)
///Default range of a sound.
#define SOUND_RANGE 17
#define MEDIUM_RANGE_SOUND_EXTRARANGE -5
+2
View File
@@ -921,6 +921,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
// Debug traits
/// This object has light debugging tools attached to it
#define TRAIT_LIGHTING_DEBUGGED "lighting_debugged"
/// This object has sound debugging tools attached to it
#define TRAIT_SOUND_DEBUGGED "sound_debugged"
/// Gives you the Shifty Eyes quirk, rarely making people who examine you think you examined them back even when you didn't
#define TRAIT_SHIFTY_EYES "shifty_eyes"
+1
View File
@@ -492,6 +492,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SNOB" = TRAIT_SNOB,
"TRAIT_SOFTSPOKEN" = TRAIT_SOFTSPOKEN,
"TRAIT_SOOTHED_THROAT" = TRAIT_SOOTHED_THROAT,
"TRAIT_SOUND_DEBUGGED" = TRAIT_SOUND_DEBUGGED,
"TRAIT_SPACEWALK" = TRAIT_SPACEWALK,
"TRAIT_SPARRING" = TRAIT_SPARRING,
"TRAIT_SPEAKS_CLEARLY" = TRAIT_SPEAKS_CLEARLY,
@@ -82,6 +82,7 @@
/obj/item/storage/box/material=1,
/obj/item/uplink/debug=1,
/obj/item/uplink/nuclear/debug=1,
/obj/item/clothing/ears/earmuffs/debug = 1,
)
generate_items_inside(items_inside,src)
+17 -9
View File
@@ -51,6 +51,9 @@
if (!turf_source || !soundin || !vol)
return
if(vol < SOUND_AUDIBLE_VOLUME_MIN) // never let sound go below SOUND_AUDIBLE_VOLUME_MIN or bad things will happen
return
//allocate a channel if necessary now so its the same for everyone
channel = channel || SSsounds.random_available_channel()
@@ -64,8 +67,9 @@
var/turf/above_turf = GET_TURF_ABOVE(turf_source)
var/turf/below_turf = GET_TURF_BELOW(turf_source)
if(ignore_walls)
var/audible_distance = CALCULATE_MAX_SOUND_AUDIBLE_DISTANCE(vol, maxdistance, falloff_distance, falloff_exponent)
if(ignore_walls)
if(above_turf && istransparentturf(above_turf))
listeners += SSmobs.clients_by_zlevel[above_turf.z]
@@ -73,16 +77,16 @@
listeners += SSmobs.clients_by_zlevel[below_turf.z]
else //these sounds don't carry through walls
listeners = get_hearers_in_view(maxdistance, turf_source)
listeners = get_hearers_in_view(audible_distance, turf_source)
if(above_turf && istransparentturf(above_turf))
listeners += get_hearers_in_view(maxdistance, above_turf)
listeners += get_hearers_in_view(audible_distance, above_turf)
if(below_turf && istransparentturf(turf_source))
listeners += get_hearers_in_view(maxdistance, below_turf)
listeners += get_hearers_in_view(audible_distance, below_turf)
for(var/mob/listening_mob in listeners | SSmobs.dead_players_by_zlevel[source_z])//observers always hear through walls
if(get_dist(listening_mob, turf_source) <= maxdistance)
if(get_dist(listening_mob, turf_source) <= audible_distance)
listening_mob.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
. += listening_mob
@@ -122,15 +126,16 @@
else
sound_to_use.frequency = get_rand_frequency()
var/distance = 0
if(isturf(turf_source))
var/turf/turf_loc = get_turf(src)
//sound volume falloff with distance
var/distance = get_dist(turf_loc, turf_source) * distance_multiplier
distance = get_dist(turf_loc, turf_source) * distance_multiplier
if(max_distance) //If theres no max_distance we're not a 3D sound, so no falloff.
sound_to_use.volume -= (max(distance - falloff_distance, 0) ** (1 / falloff_exponent)) / ((max(max_distance, distance) - falloff_distance) ** (1 / falloff_exponent)) * sound_to_use.volume
//https://www.desmos.com/calculator/sqdfl8ipgf
sound_to_use.volume -= CALCULATE_SOUND_VOLUME(vol, distance, max_distance, falloff_distance, falloff_exponent)
if(pressure_affected)
//Atmosphere affects sound
@@ -151,7 +156,7 @@
sound_to_use.volume *= pressure_factor
//End Atmosphere affecting sound
if(sound_to_use.volume <= 0)
if(sound_to_use.volume < SOUND_AUDIBLE_VOLUME_MIN)
return //No sound
var/dx = turf_source.x - turf_loc.x // Hearing from the right/left
@@ -176,6 +181,9 @@
sound_to_use.echo[3] = 0 //Room setting, 0 means normal reverb
sound_to_use.echo[4] = 0 //RoomHF setting, 0 means normal reverb.
if(HAS_TRAIT(src, TRAIT_SOUND_DEBUGGED))
to_chat(src, span_admin("Max Range-[max_distance] Distance-[distance] Vol-[round(sound_to_use.volume, 0.01)] Sound-[sound_to_use.file]"))
SEND_SOUND(src, sound_to_use)
/proc/sound_to_playing_players(soundin, volume = 100, vary = FALSE, frequency = 0, channel = 0, pressure_affected = FALSE, sound/S)
+5
View File
@@ -27,3 +27,8 @@
AddElement(/datum/element/earhealing)
AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS))
AddComponent(/datum/component/adjust_fishing_difficulty, -2)
/obj/item/clothing/ears/earmuffs/debug
name = "debug earmuffs"
desc = "Wearing these sends a chat message for every sound played. Walking to ignore footsteps is highly recommended."
clothing_traits = list(TRAIT_SOUND_DEBUGGED)