A few days ago I was testing my new industrial-themed level, and I noticed the elevator sound from a room quite a walking distance away, way too loud. When I went to wireframe, I understood why. In real space, it was right next to my character, just on the other side of a couple of meters of 'rock', which of course is really just the empty space between triangles in the level.
It was clear that I was going to need a way to more properly model sound propagation in the game.
So, I read some presentations online, most usefully the .ppt by Chris Carillo, and set about implementing a sound propagation system of my own. It took a couple of days to make it solid, but now it works great.
The main idea is to piggyback sound transport onto the existing cell/portal system.
With this system, listeners either hear a sound directly if they are in the same cell, or through a portal if they are not. Sound attenuates with the total path distance from the sound source through each portal, then to the listener. The sound seems to be emanating from the portal(s) nearest the player.
Here's the overview of how it works : There are two main parts to the system :
a) Sound propagation
b) Listener processing
Here is how the sound propagation works :
I. For each sound, find out what cell it's in, the what portals are in that cell.
II. For each of those portals, put them in a list along with the distance from the sound source to the portal.
a. If the distance is too far to hear, skip this one
III. For the 'other' cell of each portal, recursively call it
IV. For each new portal, measure the linear distance from the last portal, to this one
a. Look in list to see if this portal has been visited already
b. If so, if the new total path distance is cheaper than the old one, erase the old one
c. Else, skip this, the old way was a more direct route
d. If it's too far to hear along the total distance from the original sound through all portals to this one, skip it
V. Recursively go to step 2
Now each sound instance has a list of each portal that is transmitting the original sound, along with the total linear distance through however many portals.
Now, for listener processing :
I. Find the cell the listener is in
II. Find the cell the sound is in
III. If the same cell, process as normal, with sound coming from actual sound location
IV. If not in the same cell, see if any of the portals in my current cell are transmitting the sound
V. If not, I can't hear it
VI. If so, find the one or two portals with the closest total path distance to the sound
VII. Create one or two virtual sound locations, each pushed back along the vector from the listener through the portal, to the total sound distance
VIII. Find the relative distance to the two portals, and perform a weighted average of virtual sound location
Some of the complications include the fact that both a sound and a listener can be in more than one cell at once.
A listener may straddle two cells and a portal.
There may be more than one portal into a room, and multiple valid paths through the various cells.
If you aren't careful about computing your distances correctly, always saving the closest sound path, and performing the two-portal weighted average, you will get very audible discontinuities when you go from cell->portal->cell.
One additional trick I added to the listener->portal sound distance check was to use not the distance from the sound->portal center but the sound->closest point on the portal.
This helps prevent artifacts where the sound is supposed to be coming from the listener's left, but he's walking through a north-south portal in such a way that the portal center is on his right.
I had to add another check to the case when a char was in multiple cells, and the two cells fought over who gets the portal. Portals are treated one-way sound-wise, so sound always travels on the shortest path between the sound source, and the listener. If one cell's version of the portal went first, and that cell was further away than the other, the sound would seem to be coming backwards through the portal. A little dot product fixed this, so that in the case of fighting cells, any portal only is considered valid if it is on the same side of the player as the cell center.
Making the sound sources and sound listeners not be points, but 2x2x2 meter boxes also helped with cell->portal boundaries.
Anyway, the upshot of this is if the cells are laid out reasonably, you get very nice sound transitions around corners and through doorways. Footsteps coming down a hall sound so much better as well.
You can also hook up other gameplay objects as listeners with this system, either via polling, or via a notification system, where each valid sound source ( the initial source, plus all portals ) notifies anyone nearby. This would allow player or designer-placed microphones, sound-triggered traps, etc that are more accurate than just using distance attenuation.
A couple of extensions that I haven't tried yet include giving each room a muffling factor that would make sound traveling through the room lose ( or gain ) volume the longer it travels through the medium.